1
0

audio.go 2.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081
  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, not the frontend.
  18. //
  19. //wails:ignore
  20. func (s *Service) AudioMiddleware(next http.Handler) http.Handler {
  21. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  22. if !strings.HasPrefix(r.URL.Path, AudioPrefix) {
  23. next.ServeHTTP(w, r)
  24. return
  25. }
  26. s.serveAudio(w, r)
  27. })
  28. }
  29. func (s *Service) serveAudio(w http.ResponseWriter, r *http.Request) {
  30. slot, err := strconv.Atoi(strings.TrimPrefix(r.URL.Path, AudioPrefix))
  31. if err != nil || slot < 0 || slot >= project.Pads {
  32. http.NotFound(w, r)
  33. return
  34. }
  35. // Unlock by defer, so a panic cannot leave the lock held.
  36. v, src, err := func() (project.Pad, string, error) {
  37. s.mu.Lock()
  38. defer s.mu.Unlock()
  39. v, err := s.p.Pad(slot)
  40. src, _ := s.p.Source(v.File)
  41. return v, src, err
  42. }()
  43. if err != nil || v.Empty() || src == "" {
  44. http.NotFound(w, r)
  45. return
  46. }
  47. w.Header().Set("Cache-Control", "no-store")
  48. if r.URL.Query().Get("pcm") == "" {
  49. w.Header().Set("Content-Type", "audio/wav")
  50. http.ServeFile(w, r, src)
  51. return
  52. }
  53. // Decoded: a 12-byte header (sample rate, channels, frames as uint32
  54. // little-endian), then each channel's frames as float32 little-endian.
  55. info, chans, err := wav.DecodeFile(src)
  56. if err != nil {
  57. http.Error(w, err.Error(), http.StatusUnprocessableEntity)
  58. return
  59. }
  60. frames := len(chans[0])
  61. buf := make([]byte, 12+4*frames*len(chans))
  62. binary.LittleEndian.PutUint32(buf[0:], uint32(info.SampleRate))
  63. binary.LittleEndian.PutUint32(buf[4:], uint32(len(chans)))
  64. binary.LittleEndian.PutUint32(buf[8:], uint32(frames))
  65. off := 12
  66. for _, ch := range chans {
  67. for _, v := range ch {
  68. binary.LittleEndian.PutUint32(buf[off:], math.Float32bits(v))
  69. off += 4
  70. }
  71. }
  72. w.Header().Set("Content-Type", "application/octet-stream")
  73. w.Write(buf)
  74. }