-
Notifications
You must be signed in to change notification settings - Fork 15
/
demo.go
278 lines (232 loc) · 7.45 KB
/
demo.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
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
// Demo usage of the go-sound Sounds library, to play Clair de Lune.
package main
import (
// "flag"
"fmt"
"math"
"runtime"
// "github.com/padster/go-sound/cq"
// file "github.com/padster/go-sound/file"
"github.com/padster/go-sound/output"
s "github.com/padster/go-sound/sounds"
"github.com/padster/go-sound/util"
pm "github.com/rakyll/portmidi"
)
const (
// ~65 bpm ~= 927 ms/b ~= 309 ms/quaver (in 9/8)
q = float64(309)
)
// Notes in a treble clef, centered on B (offset 8)
var trebleMidi = [...]int{57, 59, 60, 62, 64, 65, 67, 69, 71, 72, 74, 76, 77, 79, 81, 83, 84}
// Key is D♭-Major = five flats: A♭, B♭, D♭, E♭, G♭
var trebleKeys = [...]int{-1, -1, 00, -1, -1, 00, -1, -1, -1, 00, -1, -1, 00, -1, -1, -1, 00}
func main() {
// NOTE: Not required, but shows how this can run on multiple threads.
runtime.GOMAXPROCS(3)
// Switch on whichever demo you'd like here:
if false {
renderMidi()
} else {
playClairDeLune()
}
}
// renderMidi reads a midi device, converts it to a live sound, and renders the waveform to screen.
func renderMidi() {
fmt.Println("Loading portmidi...")
pm.Initialize()
midi := s.NewMidiInput(pm.DeviceID(3)) // ++See below
fmt.Printf("Rendering midi...\n")
/*
// Note: to find your device id, use a version of this:
for i := 1; i <= pm.CountDevices(); i++ {
fmt.Printf("Reading device: %d\n", i)
di := pm.GetDeviceInfo(pm.DeviceId(i))
fmt.Printf("Info = %v\n", di)
}
*/
// Render the generated sine wave to screen:
// output.Render(midi, 2000, 500, 1)
// ...or, play it live:
output.Play(midi)
/*
sampleRate := s.CyclesPerSecond
octaves := 7
minFreq := flag.Float64("minFreq", 55.0, "minimum frequency")
maxFreq := flag.Float64("maxFreq", 55.0*float64(cq.UnsafeShift(octaves)), "maximum frequency")
bpo := flag.Int("bpo", 48, "Buckets per octave")
flag.Parse()
params := cq.NewCQParams(sampleRate, *minFreq, *maxFreq, *bpo)
spectrogram := cq.NewSpectrogram(params)
midi.Start()
defer midi.Stop()
columns := spectrogram.ProcessChannel(midi.GetSamples())
toShow := util.NewSpectrogramScreen(882, *bpo*octaves, *bpo)
toShow.Render(columns, 1)
*/
}
// playClairDeLune builds then plays Clair de Lune (Debussey)
// music from http://www.piano-midi.de/noten/debussy/deb_clai.pdf
func playClairDeLune() {
fmt.Println("Building sound.")
finalNoteLength := float64(3 + 6) // 6 extra beats, just for effect
// Left-hand split for a bit near the end.
rh1 := s.ConcatSounds(
notesT(7, fs(1)),
notesTQRun(0, 1, 0, 3, 0, -1, 0),
notesT(2, fs(-1)), notesTQRun(-2, -1), notesT(3, fs(-2)), notesT(finalNoteLength, fs(-3)),
)
rh2 := s.ConcatSounds(
notesT(6, fs(-1)),
notesT(6, fs(-2)), notesT(3, fs(-2)),
notesT(6, fs(-4)), notesT(finalNoteLength, fs(-4)),
)
// Split of couplets over long Bb
couplets := s.SumSounds(
s.ConcatSounds(notesT(1.5, fs(2)), notesT(3, fs(4)), notesT(2.5, fs(2))),
notesT(7, fs(0)),
)
// Top half of the score:
rightHand := s.ConcatSounds(
rest(2), notesT(4, fs(4, 6)), notesT(4, fs(2, 4)),
notesT(1, fs(1, 3)), notesT(1, fs(2, 4)), notesT(7, fs(1, 3)),
notesT(1, fs(0, 2)), notesT(1, fs(1, 3)), couplets,
notesT(1, fs(-1, 1)), notesT(1, fs(0, 2)), s.SumSounds(rh1, rh2),
)
// Bottom half.
leftHand := s.ConcatSounds(
rest(1), notesT(8, fs(-1, -3)),
notesT(9, fs(-0.5, -2)),
notesT(9, fs(-1, -3)),
notesT(9, fs(-2, -4)),
notesT(6, fs(-4, -5)),
notesT(3, fs(-4, -6)),
notesT(6, fs(-5, -7)), // HACK: Actually in bass clef, but rewritten in treble for these two chords.
notesT(finalNoteLength, fs(-6, -7.5)),
)
clairDeLune := s.SumSounds(leftHand, rightHand)
// toPlay := s.NewDenseIIR(clairDeLune,
// []float64{0.8922, -2.677, 2.677, -0.8922},
// []float64{2.772, -2.57, 0.7961},
// )
toPlay := clairDeLune
// hz := 440.0
// toPlay := s.SumSounds(
// s.NewSineWave(hz),
// s.NewSquareWave(hz),
// s.NewSawtoothWave(hz),
// s.NewTriangleWave(hz),
// )
// toPlay := s.NewJackInput("go-sound-in")
// toPlay := s.NewTimedSound(s.NewSineWave(500), 1000)
// toPlay := s.SumSounds(s1, s2)
// toPlay := s.NewTimedSound(shephardTones(), 10000)
// toPlay := file.Read("greatgig.flac")
// file.Write(toPlay, "gg.wav")
// fmt.Printf("Playing: \n\t%s\n", toPlay)
// output.Render(toPlay, 2000, 400)
// output.PlayJack(toPlay)
output.Play(toPlay)
// output.Play(s.LoadFlacAsSound("toneslide.flac"))
// Optional: Write to a .wav file:
// clairDeLune.Reset()
// fmt.Println("Writing sound to file.")
// file.Write(clairDeLune, "clairdelune.wav")
// Optional: Draw to screen:
// clairDeLune.Reset()
// fmt.Println("Drawing sound to screen.")
// output.Render(clairDeLune, 2000, 400)
}
// fs is a short way to write an array of floats.
func fs(fs ...float64) []float64 {
return fs
}
// The Sound of silence for quaverCount quavers
func rest(quaverCount float64) s.Sound {
return s.NewTimedSilence(q * quaverCount)
}
// A chord of notes in the treble clef, 0 = B, then notes up and down (e.g. -4 = E, 4 = F)
// in the proper key (Db major), with +/- 0.5 signifying a sharp or flat.
func notesT(quaverCount float64, notes []float64) s.Sound {
sounds := make([]s.Sound, len(notes), len(notes))
for i, note := range notes {
sounds[i] = noteTMidi(note, quaverCount)
}
return s.SumSounds(sounds...)
}
// A run of quavers in the treble clef
func notesTQRun(notes ...float64) s.Sound {
sounds := make([]s.Sound, len(notes), len(notes))
for i, note := range notes {
sounds[i] = noteTMidi(note, 1.0)
}
return s.ConcatSounds(sounds...)
}
// Converts a treble note offset to a midi offset
func noteTMidi(note float64, quaverCount float64) s.Sound {
// NOTE: Only [-8, 8] allowed for 'note'.
bFloat, sharp := math.Modf(note)
base := int(bFloat)
if sharp < 0 {
sharp += 1.0
base--
}
// 0 = B = offset 8
midi := trebleMidi[base+8] + trebleKeys[base+8]
if sharp > 0.1 {
midi++
}
hz := util.MidiToHz(midi)
// Simple waves:
// asSound := s.NewSquareWave(hz) // Try different types...
// asSound := s.NewSineWave(hz)
// asSound = s.NewADSREnvelope(asSound, 15, 50, 0.5, 20)
// String-like wave:
asSound := s.NewKarplusStrong(hz, 0.9)
return s.NewTimedSound(asSound, quaverCount*q)
}
// Shephard tones
func shephardTones() s.Sound {
octaves := 5
base, mid := 110.0, 155.563491861
tones := 2 * octaves
bases := make([]float64, tones, tones)
for i := 0; i < octaves; i++ {
bases[2*i] = base * float64(unsafeShift(i))
bases[2*i+1] = mid * float64(unsafeShift(i))
}
secondsPerOctave := 10
maxHz := bases[0] * float64(unsafeShift(octaves))
downOctaves := 1.0 / float64(unsafeShift(octaves))
samplesPerOctave := int(secondsPerOctave * s.CyclesPerSecond)
octavesPerSample := 1.0 / float64(samplesPerOctave)
channels := make([]chan []float64, tones, tones)
for i := 0; i < tones; i++ {
channels[i] = make(chan []float64)
}
go func() {
for {
for sample := 0; sample < octaves*samplesPerOctave; sample++ {
for i := 0; i < tones; i++ {
hz := bases[i] * math.Pow(2.0, float64(sample)*octavesPerSample)
if hz >= maxHz {
hz *= downOctaves
}
channels[i] <- []float64{hz, gaussianAmplitude(hz, bases[0], maxHz)}
}
}
}
}()
sounds := make([]s.Sound, tones, tones)
for i, v := range channels {
sounds[i] = s.NewHzFromChannelWithAmplitude(v)
}
return s.SumSounds(sounds...)
}
func gaussianAmplitude(at float64, minHz float64, maxHz float64) float64 {
lHalf := 0.5 * (math.Log(minHz) + math.Log(maxHz))
diff := (math.Log(at) - lHalf)
return math.Exp(-1.0 * diff * diff)
}
func unsafeShift(s int) int {
return 1 << uint(s)
}