audio.go (view raw)
1package captcha
2
3import (
4 "bytes"
5 crand "crypto/rand"
6 "encoding/binary"
7 "math"
8 "os"
9 "rand"
10 "io"
11)
12
13const sampleRate = 8000 // Hz
14
15var (
16 // Length of the longest number sound
17 longestNumSndLen int
18 endingBeepSound []byte
19)
20
21func init() {
22 for _, v := range numberSounds {
23 if longestNumSndLen < len(v) {
24 longestNumSndLen = len(v)
25 }
26 }
27 endingBeepSound = changeSpeed(beepSound, 1.4)
28}
29
30type Audio struct {
31 body *bytes.Buffer
32}
33
34// NewImage returns a new audio captcha with the given slice of numbers, where
35// each number must be in range 0-9.
36func NewAudio(numbers []byte) *Audio {
37 numsnd := make([][]byte, len(numbers))
38 nsdur := 0
39 for i, n := range numbers {
40 snd := randomizedNumSound(n)
41 nsdur += len(snd)
42 numsnd[i] = snd
43 }
44 // Intervals between numbers (including beginning)
45 intervals := make([]int, len(numbers)+1)
46 intdur := 0
47 for i := range intervals {
48 // 1 to 3 seconds
49 dur := rnd(sampleRate, sampleRate*3)
50 intdur += dur
51 intervals[i] = dur
52 }
53 // Background noise
54 bg := makeBackgroundSound(longestNumSndLen*len(numbers) + intdur)
55 // --
56 a := new(Audio)
57 sil := makeSilence(sampleRate / 5)
58 bufcap := 3*len(beepSound) + 2*len(sil) + len(bg) + len(endingBeepSound)
59 a.body = bytes.NewBuffer(make([]byte, 0, bufcap))
60 // Prelude, three beeps
61 a.body.Write(beepSound)
62 a.body.Write(sil)
63 a.body.Write(beepSound)
64 a.body.Write(sil)
65 a.body.Write(beepSound)
66 // Numbers
67 pos := intervals[0]
68 for i, v := range numsnd {
69 mixSound(bg[pos:], v)
70 pos += len(v) + intervals[i+1]
71 }
72 a.body.Write(bg)
73 // Ending
74 a.body.Write(endingBeepSound)
75 return a
76}
77
78// NewRandomAudio generates a sequence of random numbers with the given length,
79// and returns a new audio captcha with this numbers, and the sequence of
80// numbers itself.
81func NewRandomAudio(length int) (a *Audio, numbers []byte) {
82 numbers = randomNumbers(length)
83 a = NewAudio(numbers)
84 return
85}
86
87// WriteTo writes captcha audio in WAVE format into the given io.Writer, and
88// returns the number of bytes written and an error if any.
89func (a *Audio) WriteTo(w io.Writer) (n int64, err os.Error) {
90 nn, err := w.Write(waveHeader)
91 n = int64(nn)
92 if err != nil {
93 return
94 }
95 err = binary.Write(w, binary.LittleEndian, uint32(a.body.Len()))
96 if err != nil {
97 return
98 }
99 nn += 4
100 n, err = a.body.WriteTo(w)
101 n += int64(nn)
102 return
103}
104
105// mixSound mixes src into dst. Dst must have length equal to or greater than
106// src length.
107func mixSound(dst, src []byte) {
108 for i, v := range src {
109 av := int(v)
110 bv := int(dst[i])
111 if av < 128 && bv < 128 {
112 dst[i] = byte(av * bv / 128)
113 } else {
114 dst[i] = byte(2*(av+bv) - av*bv/128 - 256)
115 }
116 }
117}
118
119func setSoundLevel(a []byte, level float64) {
120 for i, v := range a {
121 av := float64(v)
122 switch {
123 case av > 128:
124 if av = (av-128)*level + 128; av < 128 {
125 av = 128
126 }
127 case av < 128:
128 if av = 128 - (128-av)*level; av > 128 {
129 av = 128
130 }
131 default:
132 continue
133 }
134 a[i] = byte(av)
135 }
136}
137
138// changeSpeed returns new PCM bytes from the bytes with the speed and pitch
139// changed to the given value that must be in range [0, x].
140func changeSpeed(a []byte, pitch float64) []byte {
141 b := make([]byte, int(math.Floor(float64(len(a))*pitch)))
142 var p float64
143 for _, v := range a {
144 for i := int(p); i < int(p+pitch); i++ {
145 b[i] = v
146 }
147 p += pitch
148 }
149 return b
150}
151
152// rndFloat64n returns a random float64 number in range [from, to].
153func rndFloat64n(from, to float64) float64 {
154 return (to-from)*rand.Float64() + from
155}
156
157func randomSpeed(a []byte) []byte {
158 pitch := rndFloat64n(0.9, 1.2)
159 return changeSpeed(a, pitch)
160}
161
162func makeSilence(length int) []byte {
163 b := make([]byte, length)
164 for i := 0; i < length; i++ {
165 b[i] = 128
166 }
167 return b
168}
169
170func makeWhiteNoise(length int, level uint8) []byte {
171 noise := make([]byte, length)
172 _, err := io.ReadFull(crand.Reader, noise)
173 if err != nil {
174 panic("error reading from random source: " + err.String())
175 }
176 for i := 0; i < len(noise); i++ {
177 noise[i] %= level
178 noise[i] += 128 - level/2
179 }
180 return noise
181}
182
183func reversedSound(a []byte) []byte {
184 n := len(a)
185 b := make([]byte, n)
186 for i, v := range a {
187 b[n-1-i] = v
188 }
189 return b
190}
191
192func makeBackgroundSound(length int) []byte {
193 b := makeWhiteNoise(length, 8)
194 for i := 0; i < length/(sampleRate/10); i++ {
195 snd := numberSounds[rand.Intn(10)]
196 snd = changeSpeed(reversedSound(snd), rndFloat64n(0.8, 1.4))
197 place := rand.Intn(len(b) - len(snd))
198 setSoundLevel(snd, rndFloat64n(0.5, 1.2))
199 mixSound(b[place:], snd)
200 }
201 setSoundLevel(b, rndFloat64n(0.2, 0.3))
202 return b
203}
204
205func randomizedNumSound(n byte) []byte {
206 s := randomSpeed(numberSounds[n])
207 setSoundLevel(s, rndFloat64n(0.7, 1.3))
208 return s
209}