audio.go 3.9 KB

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