Skip to content

Commit

Permalink
Added adsr envelope
Browse files Browse the repository at this point in the history
  • Loading branch information
aprice2704 committed Jan 28, 2021
1 parent e8e8fbc commit 0c0b257
Show file tree
Hide file tree
Showing 12 changed files with 319 additions and 197 deletions.
194 changes: 113 additions & 81 deletions envelopes.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,128 +11,160 @@ package main
// They should be normalized to have values between 0 and 1

import (
"fmt"
"math"
)

// PlanckTime is the shortest possible interval of time
const (
PlanckTime Seconds = 1e-20
)

var (
sqrt2π float64 = math.Sqrt(τ) // simple optimization
)

// Enveloper modulates a signal
type Enveloper interface {
Amplitude(t Seconds) float64 // Call this from outside
OnePeriodAmplitude(t Seconds) float64 // Implement this
Length() Seconds // Return overall length of the envelope
Amplitude(t Seconds) Volts // Call this from outside
Length() Seconds // Return overall length of the envelope
}

// Envelope is the 'base' type for envelopes
type Envelope struct {
λ Seconds // Period of repeat
Repeats bool // Does it repeat or is it single shot?
Len Seconds // The overall length of the envelope (might be several λ long)
T0 Seconds // *Global* time when the envelope starts
λ Seconds // Period of repeat
// Repeats bool // Does it repeat or is it single shot?
Len Seconds // The overall length of the envelope (might be several λ long)
}

// NewEnvelope is self-evident
func NewEnvelope(λ Seconds, reps bool, l Seconds) *Envelope {
return &Envelope{λ: λ, Repeats: reps, Len: l}
// NewEnvelope is
func NewEnvelope(t0 Seconds, λ Seconds, reps bool, l Seconds) *Envelope {
return &Envelope{T0: t0, λ: λ, Len: l}
}

// Gaussian is an envelope with height 1 at μ and RMS width of σ
// f(x) = exp(-(x-μ)^2/2σ^2) μ and σ should be specified in seconds
type Gaussian struct {
// ADSR is a classic ADSR envelope
type ADSR struct {
Envelope
μ, σ Seconds
σσ Seconds
Ta Seconds // Attack time (0->1)
Td Seconds // Decay (1->Ls)
Ls Volts // Sustain level (Ls)
TsMax Seconds // Maximum sustain time
TsMin Seconds // Minimum sustain time
Tr Seconds // Release time (Ls->0)
sStart LocalSeconds // when did sustain begin?
knowRelease bool // Is release time known?
releaseAt LocalSeconds // when released
tsActual Seconds // Actual sustain time (derived from keyup etc.)
}

// NewGaussian makes a new one
func NewGaussian(λ Seconds, reps bool, l Seconds, newμ, newσ Seconds) *Gaussian {
e := NewEnvelope(λ, reps, l)
g := &Gaussian{Envelope: *e, μ: newμ, σ: newσ, σσ: newσ * newσ}
return g
// NewADSR makes a new one, pass ts as zero if not known at creation
func NewADSR(t0 Seconds, reps bool, ta Seconds, td Seconds, ls Volts, tsmax Seconds, tsmin Seconds, ts Seconds) *ADSR {
if tsmax < PlanckTime {
tsmax = MaxNoteLen
}
adsr := ADSR{Ta: ta, Td: td, Ls: ls, TsMax: tsmax, TsMin: tsmin, tsActual: ts, sStart: LocalSeconds(ta + td)}
adsr.Envelope = Envelope{T0: t0}
if ts > PlanckTime { // we know when release happens
adsr.releaseAt = LocalSeconds(ts + ta + td)
adsr.knowRelease = true
}
return &adsr
}

// OnePeriodAmplitude fulfils Envelope interface
func (g *Gaussian) OnePeriodAmplitude(x Seconds) float64 {
// Release triggers the release at the given global time. Strangeness will result if called after release should have started
func (adsr ADSR) Release(t Seconds) {
tLocal := LocalSeconds(t - adsr.T0)
ts := tLocal - adsr.sStart // length of the sustain
tsact := max(min(Seconds(ts), adsr.TsMin), adsr.TsMax) // clip into valid range
adsr.releaseAt = LocalSeconds(tsact)
adsr.knowRelease = true
}

// Amplitude is
func (adsr ADSR) Amplitude(t Seconds) Volts {
localT := LocalSeconds(t - adsr.T0)
var a Volts
switch {
case localT < LocalSeconds(adsr.Ta):
a = Volts(localT / LocalSeconds(adsr.Ta))
case localT < adsr.sStart:
a = 1 - Volts((localT-LocalSeconds(adsr.Ta))*(1-LocalSeconds(adsr.Ls))/LocalSeconds(adsr.Td))
case localT < adsr.releaseAt:
a = adsr.Ls
case localT > adsr.releaseAt:
if !adsr.knowRelease {
fmt.Printf("ADSR envelope error: in release stage of unreleased envelope")
}
default:
fmt.Printf("ADSR envelope error: in unknown portion of envelope")
}
return a
}

func max(a, b Seconds) Seconds {
if a > b {
return a
}
return b
}

xu := float64(x - g.μ)
return math.Exp(-xu * xu / float64(2*g.σσ))
func min(a, b Seconds) Seconds {
if a < b {
return a
}
return b
}

// Triangle a simple /\ with period λ
type Triangle struct {
*Envelope
Envelope
}

// NewTriangle is self-evident
func NewTriangle(λ Seconds, reps bool, l Seconds) *Triangle {
e := NewEnvelope(λ, reps, l)
return &Triangle{Envelope: e}
// NewTriangle makes one
func NewTriangle(t Seconds, λ Seconds, reps bool, l Seconds) *Triangle {
tr := Triangle{}
tr.Envelope = Envelope{T0: t, λ: λ, Len: l}
// fmt.Printf("New triangle at %f\n", t)
return &tr
}

// Amplitude is
func (tr Triangle) Amplitude(t Seconds) float64 {
return tr.OnePeriodAmplitude(Seconds(math.Mod(float64(t), float64(tr.λ))))
func (tr Triangle) Amplitude(t Seconds) Volts {
localT := t - tr.T0
return tr.onePeriodAmplitude(LocalSeconds(math.Mod(float64(localT), float64(tr.λ))))
}

// OnePeriodAmplitude is
func (tr Triangle) OnePeriodAmplitude(t Seconds) float64 {
// s := math.Mod(float64(t), float64(tr.λ))
// onePeriodAmplitude is
func (tr Triangle) onePeriodAmplitude(t LocalSeconds) Volts {
if Seconds(t) < (tr.λ)/2 {
return float64(t * 2 / tr.λ)
return Volts(Seconds(t) * 2 / tr.λ)
}
return float64(2 - (t * 2 / tr.λ))
return Volts(2 - (Seconds(t) * 2 / tr.λ))
}

// Length is
func (tr Triangle) Length() Seconds {
return tr.Len
}

// RepeatAmplitude is
// func (tr *Triangle) RepeatAmplitude(t Seconds) float64 {
// s := math.Mod(float64(t), float64(tr.λ))
// if Seconds(s) < (tr.λ)/2 {
// return s * 2 / float64(tr.λ)
// }
// return 2 - (s * 2 / float64(tr.λ))
// }
// SetPeriodandLength fulfils Enveloper interface
// func (tr Triangle) SetPeriodandLength(λ Seconds, length Seconds) {
// tr.Envelope.SetPeriodandLength(λ, length)
// }

// RepeatAmplitude generates a sequence of gaussian envelopes of period λ seconds
// func (g *Gaussian) OneShotAmplitude(t Seconds) float64 {

// s := math.Mod(float64(t), float64(g.λ))
// tu := s - float64(g.μ)
// return math.Exp(-tu * tu / float64(2*g.σσ))

// }
// OnePeriodAmplitude is the function you should implement for new types of envelope
// func (e *Envelope) OnePeriodAmplitude(t Seconds) float64 {
// fmt.Printf("Warning: Amplitude called on base Envelope class, probably an error\n")
// return 1
// }

// Amplitude is the function to call from outide the class to get either the single shot envelope or the repeated one
// If you implemented OneShotAmplitude, this should give you repeats for free
// func (e *Envelope) Amplitude(t Seconds) float64 {
// if e.Repeats {
// return e.OnePeriodAmplitude(Seconds(math.Mod(float64(t), float64(e.λ))))
// }
// return e.OnePeriodAmplitude(t)
// }
// Gaussian is an envelope with height 1 at μ and RMS width of σ
// f(x) = exp(-(x-μ)^2/2σ^2) μ and σ should be specified in seconds
type Gaussian struct {
Envelope
μ, σ Seconds
σσ Seconds
}

// Length is
// func (e *Envelope) Length() Seconds {
// return e.Len
// }

// SetPeriodandLength fulfils Enveloper interface
// func (e *Envelope) SetPeriodandLength(λ Seconds, length Seconds) {
// e.λ = λ
// e.Len = length
// }
// SetPeriodandLength(λ Seconds, length Seconds) // Set both the period (λ) field and length
// NewGaussian makes a new one
func NewGaussian(globalT Seconds, λ Seconds, reps bool, l Seconds, newμ, newσ Seconds) *Gaussian {
e := NewEnvelope(globalT, λ, reps, l)
g := &Gaussian{Envelope: *e, μ: newμ, σ: newσ, σσ: newσ * newσ}
return g
}

// OnePeriodAmplitude fulfils Envelope interface
func (g *Gaussian) onePeriodAmplitude(localT Seconds) Volts {
xu := float64(localT - g.μ)
return Volts(math.Exp(-xu * xu / float64(2*g.σσ)))
}
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ go 1.15
require (
github.com/faiface/beep v1.0.2
github.com/veandco/go-sdl2 v0.4.5
gitlab.com/gomidi/midi v1.21.0
golang.org/x/image v0.0.0-20201208152932-35266b937fa6 // indirect
golang.org/x/sys v0.0.0-20210110051926-789bb1bd4061 // indirect
)
Binary file modified line.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added line.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added line2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 0c0b257

Please sign in to comment.