audio.go 4.0 KB

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