all repos — captcha @ 9ac4ea2b48fc4edeb3c4f82425b5bfdf96016fb3

Go package captcha implements generation and verification of image and audio CAPTCHAs.

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