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