1
0

audio.go 2.2 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879
  1. package app
  2. import (
  3. "encoding/binary"
  4. "math"
  5. "net/http"
  6. "strconv"
  7. "strings"
  8. "git.monoexp.com/bajdev/mpckit/internal/project"
  9. "git.monoexp.com/bajdev/mpckit/internal/wav"
  10. )
  11. // AudioPrefix is the URL path the preview loads samples from:
  12. // /audio/<slot> serves the pad's source WAV as it is, and
  13. // /audio/<slot>?pcm=1 serves it decoded, for formats the webview cannot
  14. // decode (build plan 4.5 and section 9).
  15. const AudioPrefix = "/audio/"
  16. // AudioMiddleware serves AudioPrefix and passes every other request on.
  17. // It is for main.go's asset server.
  18. func (h *Host) AudioMiddleware(next http.Handler) http.Handler {
  19. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  20. if !strings.HasPrefix(r.URL.Path, AudioPrefix) {
  21. next.ServeHTTP(w, r)
  22. return
  23. }
  24. h.s.serveAudio(w, r)
  25. })
  26. }
  27. func (s *Service) serveAudio(w http.ResponseWriter, r *http.Request) {
  28. slot, err := strconv.Atoi(strings.TrimPrefix(r.URL.Path, AudioPrefix))
  29. if err != nil || slot < 0 || slot >= project.Pads {
  30. http.NotFound(w, r)
  31. return
  32. }
  33. // Unlock by defer, so a panic cannot leave the lock held.
  34. v, src, err := func() (project.Pad, string, error) {
  35. s.mu.Lock()
  36. defer s.mu.Unlock()
  37. v, err := s.p.Pad(slot)
  38. src, _ := s.p.Source(v.File)
  39. return v, src, err
  40. }()
  41. if err != nil || v.Empty() || src == "" {
  42. http.NotFound(w, r)
  43. return
  44. }
  45. w.Header().Set("Cache-Control", "no-store")
  46. if r.URL.Query().Get("pcm") == "" {
  47. w.Header().Set("Content-Type", "audio/wav")
  48. http.ServeFile(w, r, src)
  49. return
  50. }
  51. // Decoded: a 12-byte header (sample rate, channels, frames as uint32
  52. // little-endian), then each channel's frames as float32 little-endian.
  53. info, chans, err := wav.DecodeFile(src)
  54. if err != nil {
  55. http.Error(w, err.Error(), http.StatusUnprocessableEntity)
  56. return
  57. }
  58. frames := len(chans[0])
  59. buf := make([]byte, 12+4*frames*len(chans))
  60. binary.LittleEndian.PutUint32(buf[0:], uint32(info.SampleRate))
  61. binary.LittleEndian.PutUint32(buf[4:], uint32(len(chans)))
  62. binary.LittleEndian.PutUint32(buf[8:], uint32(frames))
  63. off := 12
  64. for _, ch := range chans {
  65. for _, v := range ch {
  66. binary.LittleEndian.PutUint32(buf[off:], math.Float32bits(v))
  67. off += 4
  68. }
  69. }
  70. w.Header().Set("Content-Type", "application/octet-stream")
  71. w.Write(buf)
  72. }