-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathbuffer.go
171 lines (155 loc) · 4.74 KB
/
buffer.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
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
package sc
import (
"errors"
"fmt"
"sync"
"github.com/scgolang/osc"
)
const (
bufIndexIncr = int32(1)
// BufferFlagNormalize causes the peak amplitude of the buffer
// to be normalized to 1.0 when using the Gen method.
BufferFlagNormalize = 0x01
// BufferFlagWavetable causes the buffer to be written in wavetable
// format when using the Gen method, so that it can be used by
// interpolating oscillators
BufferFlagWavetable = 0x02
// BufferFlagClear causes the buffer to be cleared before new
// partials are written using the Gen method. Otherwise new partials
// are summed with the existing contents of the buffer.
BufferFlagClear = 0x04
// BufferRoutineSine1 generates a buffer with a series of sine partials.
// The args of this routine are the amplitudes of the partials.
BufferRoutineSine1 = "sine1"
// BufferRoutineSine2 is similar to BufferRoutineSine1 except that the
// args are pairs of frequency and amplitude (i.e. you can specify the
// frequency of each partial).
BufferRoutineSine2 = "sine2"
// BufferRoutineSine3 is similar to BufferRoutineSine1 except that the
// args are triplets of frequency, amplitude, and phase (i.e. you can
// specify the frequency and phase of each partial).
BufferRoutineSine3 = "sine3"
// BufferRoutineCheby generates a buffer that contains a series of
// chebyshev polynomials which can be defined as
// cheby(n) = amplitude * cos(n * acos(x))
// The first arg specifies the amplitude for n = 1, the second arg
// specifies the amplitude for n = 2, and so on.
// To eliminate DC offset when used as a waveshaper, the wavetable is
// offset so that the center value is zero.
BufferRoutineCheby = "cheby"
)
// Buffer is a client-side representation of an scsynth audio buffer
type Buffer struct {
Channels int32
Frames int32
Num int32
SampleRate float32
client *Client
}
// Gen generates a buffer using a routine.
// A runtime panic will occur if routine is not one of the
// BufferRoutine constants.
func (buffer *Buffer) Gen(routine string, flags int, args ...float32) error {
if err := checkBufferRoutine(routine); err != nil {
return err
}
if err := checkBufferGenFlags(flags); err != nil {
return err
}
if err := buffer.sendGenMsg(routine, flags, args...); err != nil {
return err
}
if err := buffer.awaitGenReply(); err != nil {
return err
}
return nil
}
// sendGenMsg sends a /b_gen command.
func (buffer *Buffer) sendGenMsg(routine string, flags int, args ...float32) error {
msg := osc.Message{
Address: bufferGenAddress,
Arguments: osc.Arguments{
osc.Int(buffer.Num),
osc.String(routine),
osc.Int(int32(flags)),
},
}
for _, arg := range args {
msg.Arguments = append(msg.Arguments, osc.Float(arg))
}
if err := buffer.client.oscConn.Send(msg); err != nil {
return err
}
return nil
}
// awaitGenReply waits for a reply to the /b_gen command
func (buffer *Buffer) awaitGenReply() error {
var done osc.Message
select {
case done = <-buffer.client.doneChan:
case err := <-buffer.client.errChan:
return err
}
if len(done.Arguments) != 2 {
return errors.New("expected two arguments to /done message")
}
addr, err := done.Arguments[0].ReadString()
if err != nil {
return err
}
// If reply address is not /b_gen, requeue the done event.
if addr != bufferGenAddress {
buffer.client.doneChan <- done
return nil
}
bufnum, err := done.Arguments[1].ReadInt32()
if err != nil {
return err
}
// TODO: Don't error if we get a done message for a different buffer.
// We should probably requeue this particular done message on doneChan.
if bufnum != buffer.Num {
buffer.client.doneChan <- done
}
return nil
}
// checkBufferRoutine panics if routine is not one of the
// supported BufferRoutine constants
func checkBufferRoutine(routine string) error {
if routine != BufferRoutineSine1 &&
routine != BufferRoutineSine2 &&
routine != BufferRoutineSine3 &&
routine != BufferRoutineCheby {
return fmt.Errorf("unsupported buffer routine %s", routine)
}
return nil
}
// checkBufferGenFlags panics if not 0 <= flags <= 4
func checkBufferGenFlags(flags int) error {
if flags < 0 && flags > 4 {
return fmt.Errorf("unsupported buffer flags %d", flags)
}
return nil
}
// global buffer map (keys are paths to audio files on disk)
var buffers = struct {
sync.RWMutex
m map[string]*Buffer
}{m: make(map[string]*Buffer)}
// newReadBuffer creates a new buffer for /b_allocRead
func newReadBuffer(path string, num int32, c *Client) *Buffer {
buffers.RLock()
// return the existing buffer if there is one
if existing, exists := buffers.m[path]; exists {
buffers.RUnlock()
return existing
}
buffers.RUnlock()
// make a new one
b := &Buffer{Num: num, client: c}
// add it to the global map
buffers.Lock()
buffers.m[path] = b
buffers.Unlock()
return b
}