44package audio
55
66import (
7- "github.com/elgopher/pi/piaudio"
8- "github.com/elgopher/pi/pimath"
97 "log"
108 "math"
119 "slices"
1210 "sort"
1311 "sync"
1412 "unsafe"
13+
14+ "github.com/elgopher/pi/piaudio"
15+ "github.com/elgopher/pi/pimath"
1516)
1617
18+ const chanLen = 4
19+
1720func newPlayer () * player {
1821 defaultChannel := channel {
1922 pitch : 1.0 ,
@@ -25,7 +28,7 @@ func newPlayer() *player {
2528 }
2629 return & player {
2730 samplesByAddr : map [uintptr ]* piaudio.Sample {},
28- channels : [4 ]channel {
31+ channels : [chanLen ]channel {
2932 defaultChannel , defaultChannel , defaultChannel , defaultChannel ,
3033 },
3134 }
@@ -34,9 +37,9 @@ func newPlayer() *player {
3437type player struct {
3538 mutex sync.Mutex
3639 samplesByAddr map [uintptr ]* piaudio.Sample
37- channels [4 ]channel
40+ channels [chanLen ]channel
3841
39- commandsByTime [] command // all planned commands sorted by time
42+ commandsByTime [chanLen ][] command // each channel's planned commands sorted by time
4043
4144 currentTime float64
4245}
@@ -115,51 +118,48 @@ func (p *player) Read(out []byte) (n int, err error) {
115118}
116119
117120func (p * player ) runCommands () {
118- processed := 0
121+ for i := 0 ; i < chanLen ; i ++ {
122+ selectedChan := & p .channels [i ]
119123
120- for _ , cmd := range p .commandsByTime {
121- if cmd .time > p .currentTime {
122- break
123- }
124+ processed := 0
124125
125- for i := 0 ; i < 4 ; i ++ {
126- selectedChan := & p .channels [i ]
127- chanNum := piaudio .Chan (1 << i )
128- // a single command can be executed on multiple channels at once
129- if cmd .ch & chanNum == chanNum {
130- switch cmd .kind {
131- case cmdKindSetSample :
132- switch {
133- case cmd .sampleAddr == 0 :
134- selectedChan .active = false
135- selectedChan .sampleData = nil
136- case p .samplesByAddr [cmd .sampleAddr ] == nil :
137- log .Printf ("[piaudio] SetSample failed: Sample not found, addr: 0x%x" , cmd .sampleAddr )
138- selectedChan .active = false
139- selectedChan .sampleData = nil
140- default :
141- selectedChan .active = true
142- sample := p .samplesByAddr [cmd .sampleAddr ]
143- selectedChan .sampleData = sample .Data ()
144- selectedChan .sampleRate = sample .SampleRate ()
145- }
146- selectedChan .position = float64 (cmd .offset )
147- case cmdKindSetLoop :
148- selectedChan .loop = cmd .loop
149- case cmdKindSetPitch :
150- selectedChan .pitch = cmd .pitch
151- case cmdKindSetVolume :
152- selectedChan .volume = cmd .vol
153- case cmdKindClearChan :
154- // ClearChan was already called in SendCommands
126+ for _ , cmd := range p .commandsByTime [i ] {
127+ if cmd .time > p .currentTime {
128+ break
129+ }
130+
131+ switch cmd .kind {
132+ case cmdKindSetSample :
133+ switch {
134+ case cmd .sampleAddr == 0 :
135+ selectedChan .active = false
136+ selectedChan .sampleData = nil
137+ case p .samplesByAddr [cmd .sampleAddr ] == nil :
138+ log .Printf ("[piaudio] SetSample failed: Sample not found, addr: 0x%x" , cmd .sampleAddr )
139+ selectedChan .active = false
140+ selectedChan .sampleData = nil
141+ default :
142+ selectedChan .active = true
143+ sample := p .samplesByAddr [cmd .sampleAddr ]
144+ selectedChan .sampleData = sample .Data ()
145+ selectedChan .sampleRate = sample .SampleRate ()
155146 }
147+ selectedChan .position = float64 (cmd .offset )
148+ case cmdKindSetLoop :
149+ selectedChan .loop = cmd .loop
150+ case cmdKindSetPitch :
151+ selectedChan .pitch = cmd .pitch
152+ case cmdKindSetVolume :
153+ selectedChan .volume = cmd .vol
154+ case cmdKindClearChan :
155+ // ClearChan was already called in SendCommands
156156 }
157+ processed ++
157158 }
158- processed ++
159- }
160159
161- copy (p .commandsByTime , p .commandsByTime [processed :])
162- p .commandsByTime = p .commandsByTime [:len (p .commandsByTime )- processed ]
160+ copy (p .commandsByTime [i ], p .commandsByTime [i ][processed :])
161+ p .commandsByTime [i ] = p .commandsByTime [i ][:len (p .commandsByTime [i ])- processed ]
162+ }
163163}
164164
165165func (p * player ) read (out []byte ) {
@@ -215,34 +215,32 @@ func (p *player) SendCommands(cmds []command) {
215215 p .clearChan (cmd .ch , cmd .time )
216216 continue
217217 }
218- p .commandsByTime = append (p .commandsByTime , cmd )
218+ for i := 0 ; i < chanLen ; i ++ {
219+ chanNum := piaudio .Chan (1 << i )
220+ // a single command can be executed on multiple channels at once
221+ if cmd .ch & chanNum != 0 {
222+ p .commandsByTime [i ] = append (p .commandsByTime [i ], cmd )
223+ }
224+ }
219225 }
220226
221- // sort again by time, because new commands may have been inserted between existing ones
222- sort .SliceStable (p .commandsByTime , func (i , j int ) bool {
223- return p .commandsByTime [i ].time < p .commandsByTime [j ].time
224- })
227+ for _ , commands := range p .commandsByTime {
228+ // sort again by time, because new commands may have been inserted between existing ones
229+ sort .SliceStable (commands , func (i , j int ) bool {
230+ return commands [i ].time < commands [j ].time
231+ })
232+ }
225233}
226234
227- // clearChan is O(n^2).
228- // It could be optimized to use a separate command list for each channel.
229- // Then complexity will be O(n)
230235func (p * player ) clearChan (ch piaudio.Chan , time float64 ) {
231- for j := len (p .commandsByTime ) - 1 ; j >= 0 ; j -- {
232- cmd := p .commandsByTime [j ]
233- noMoreCommands := cmd .time < time
234- if noMoreCommands {
235- return
236- }
237- if cmd .ch & ch != 0 {
238- remaining := cmd .ch &^ ch
239- if remaining == 0 {
240- // remove cmd
241- copy (p .commandsByTime [j :], p .commandsByTime [j + 1 :])
242- p .commandsByTime = p .commandsByTime [:len (p .commandsByTime )- 1 ]
243- } else {
244- // update cmd to apply only to the remaining channels
245- p .commandsByTime [j ].ch = remaining
236+ for i := 0 ; i < chanLen ; i ++ {
237+ chanNum := piaudio .Chan (1 << i )
238+ if ch & chanNum != 0 {
239+ idx := slices .IndexFunc (p .commandsByTime [i ], func (c command ) bool {
240+ return c .time >= time
241+ })
242+ if idx != - 1 {
243+ p .commandsByTime [i ] = p .commandsByTime [i ][:idx ]
246244 }
247245 }
248246 }
0 commit comments