-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathosc.go
124 lines (111 loc) · 2.86 KB
/
osc.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
package generator
import (
"fmt"
"math"
"github.com/go-audio/audio"
)
// Osc is an oscillator
type Osc struct {
Shape WaveType
Amplitude float64
DcOffset float64
Freq float64
// SampleRate
Fs int
PhaseOffset float64
CurrentPhaseAngle float64
phaseAngleIncr float64
// currentSample allows us to track where we are at in the signal life
// and setup an envelope accordingly
currentSample int
// ADSR
attackInSamples int
}
// NewOsc returns a new oscillator, note that if you change the phase offset of the returned osc,
// you also need to set the CurrentPhaseAngle
func NewOsc(shape WaveType, hz float64, fs int) *Osc {
return &Osc{Shape: shape, Amplitude: 1, Freq: hz, Fs: fs, phaseAngleIncr: ((hz * TwoPi) / float64(fs))}
}
// Reset sets the oscillator back to its starting state
func (o *Osc) Reset() {
o.phaseAngleIncr = ((o.Freq * TwoPi) / float64(o.Fs))
o.currentSample = 0
}
// SetFreq updates the oscillator frequency
func (o *Osc) SetFreq(hz float64) {
if o.Freq != hz {
o.Freq = hz
o.phaseAngleIncr = ((hz * TwoPi) / float64(o.Fs))
}
}
// SetAttackInMs sets the duration for the oscillator to be at full amplitude
// after it starts.
func (o *Osc) SetAttackInMs(ms int) {
if o == nil {
return
}
if ms <= 0 {
o.attackInSamples = 0
return
}
o.attackInSamples = int(float32(o.Fs) / (1000.0 / float32(ms)))
}
// Signal uses the osc to generate a discreet signal
func (o *Osc) Signal(length int) []float64 {
output := make([]float64, length)
for i := 0; i < length; i++ {
output[i] = o.Sample()
}
return output
}
// Fill fills up the pass audio Buffer with the output of the oscillator.
func (o *Osc) Fill(buf *audio.FloatBuffer) error {
if o == nil {
return nil
}
numChans := 1
if f := buf.Format; f != nil {
numChans = f.NumChannels
}
frameCount := buf.NumFrames()
var sample float64
for i := 0; i < frameCount; i++ {
sample = o.Sample()
for j := 0; j < numChans; j++ {
buf.Data[i*numChans+j] = sample
}
}
return nil
}
// Sample returns the next sample generated by the oscillator
func (o *Osc) Sample() (output float64) {
if o == nil {
return
}
o.currentSample++
if o.CurrentPhaseAngle < -math.Pi {
o.CurrentPhaseAngle += TwoPi
} else if o.CurrentPhaseAngle > math.Pi {
o.CurrentPhaseAngle -= TwoPi
}
var amp float64
if o.attackInSamples > o.currentSample {
// linear fade in
amp = float64(o.currentSample) * (o.Amplitude / float64(o.attackInSamples))
} else {
amp = o.Amplitude
}
switch o.Shape {
case WaveSine:
output = amp*Sine(o.CurrentPhaseAngle) + o.DcOffset
case WaveTriangle:
output = amp*Triangle(o.CurrentPhaseAngle) + o.DcOffset
case WaveSaw:
output = amp*Sawtooth(o.CurrentPhaseAngle) + o.DcOffset
case WaveSqr:
fmt.Println(o.CurrentPhaseAngle)
output = amp*Square(o.CurrentPhaseAngle) + o.DcOffset
}
o.CurrentPhaseAngle += o.phaseAngleIncr
return output
}