music - music.go
1 package music // import "vimagination.zapto.org/music"
2
3 import (
4 "math"
5 "sort"
6 "sync"
7
8 "github.com/gordonklaus/portaudio"
9 )
10
11 var (
12 Initialize = portaudio.Initialize
13 Terminate = portaudio.Terminate
14 )
15
16 type sounds []sound
17
18 func (s sounds) Len() int {
19 return len(s)
20 }
21
22 func (s sounds) Less(i, j int) bool {
23 return s[i].Start < s[j].Start
24 }
25
26 func (s sounds) Swap(i, j int) {
27 s[i], s[j] = s[j], s[i]
28 }
29
30 type sound struct {
31 Wave Wave
32 Profile Profile
33 channel int
34 Start, End uint64
35 }
36
37 func (s sound) Val(rate, time float64) float64 {
38 return s.Profile(time/float64(s.End-s.Start)) * s.Wave(time/rate)
39 }
40
41 type Player struct {
42 *portaudio.Stream
43 sampleRate float64
44
45 mu sync.Mutex
46 time uint64
47 sounds sounds
48 }
49
50 func New(sampleRate float64, channels int) (*Player, error) {
51 p := &Player{sampleRate: sampleRate}
52 var err error
53 p.Stream, err = portaudio.OpenDefaultStream(0, channels, sampleRate, 0, p.process)
54 if err != nil {
55 return nil, err
56 }
57 return p, nil
58 }
59
60 func (p *Player) Add(start, length uint64, wave func(float64) float64, profile func(float64) float64, channel int) {
61 p.mu.Lock()
62 p.sounds = append(p.sounds, sound{
63 Wave: wave,
64 Profile: profile,
65 channel: channel,
66 Start: start,
67 End: start + length,
68 })
69 sort.Sort(p.sounds)
70 p.mu.Unlock()
71 }
72
73 func (p *Player) EndZeroNote(length uint64, note Note) uint64 {
74 d := p.sampleRate / float64(note)
75 i, f := math.Modf(float64(length) / d)
76 if f == 0 {
77 return length
78 }
79 return uint64((i + 1) * d)
80 }
81
82 func (p *Player) Time() uint64 {
83 p.mu.Lock()
84 t := p.time
85 p.mu.Unlock()
86 return t
87 }
88
89 func (p *Player) Rate() uint64 {
90 return uint64(p.sampleRate)
91 }
92
93 func (p *Player) process(data [][]float32) {
94 p.mu.Lock()
95 p.time = p.processMusic(data)
96 go p.update()
97 }
98
99 func (p *Player) processMusic(data [][]float32) uint64 {
100 var time uint64
101 for channel, dc := range data {
102 time = p.time
103 for i := range dc {
104 var f float64
105 for _, sound := range p.sounds {
106 if sound.channel == channel {
107 if sound.Start <= time {
108 if sound.End > time {
109 f += sound.Val(p.sampleRate, float64(time-sound.Start))
110 }
111 } else {
112 break
113 }
114 }
115 }
116 time++
117 dc[i] = float32(f)
118 }
119 }
120 return time
121 }
122
123 func (p *Player) update() {
124 changed := false
125 for i := 0; i < len(p.sounds); i++ {
126 if p.sounds[i].End <= p.time {
127 p.sounds[i] = p.sounds[len(p.sounds)-1]
128 p.sounds = p.sounds[:len(p.sounds)-1]
129 i--
130 changed = true
131 }
132 }
133 if changed {
134 sort.Sort(p.sounds)
135 }
136 p.mu.Unlock()
137 }