all repos — captcha @ 8f4b20120ff58a43a82515d9e1b9a81b30cd9a81

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	"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}