all repos — captcha @ 82004bd901963399caa43f308230f2ccb8ae5cd0

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