PageRenderTime 35ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/go/remixoscope.go

https://github.com/mkb218/remixoscope
Go | 535 lines | 459 code | 52 blank | 24 comment | 149 complexity | 55f9fb18283065a8149d827ea26600b4 MD5 | raw file
  1. package main
  2. import (
  3. "bufio"
  4. "path"
  5. "flag"
  6. "rand"
  7. "fmt"
  8. "syscall"
  9. "os"
  10. "math"
  11. binary "encoding/binary"
  12. "exec"
  13. "regexp"
  14. "strings"
  15. vector "container/vector"
  16. "strconv"
  17. "bytes"
  18. ioutil "io/ioutil"
  19. )
  20. type buffer struct {
  21. left, right []int16
  22. }
  23. type bucket struct {
  24. left, right float64
  25. }
  26. type beat struct {
  27. buckets []bucket // one frame per band
  28. }
  29. type source struct {
  30. filename string
  31. beats []beat
  32. }
  33. var sources []source
  34. var soxpath string
  35. var soxformatopts []string
  36. var tmpdir string
  37. var outputdir string
  38. var beatlength uint
  39. var bands uint
  40. var outputext string
  41. var inputfiles []string
  42. var samplerate uint
  43. var buffersize uint
  44. func shuffle(v vector.Vector) {
  45. for i := len(v) - 1; i >= 1; i-- {
  46. j := rand.Intn(i)
  47. v.Swap(i, j)
  48. }
  49. }
  50. func marshal() (outstring []string) {
  51. // <bandcount>|trackname|beat0band0lbeat0band0r...Beat0bandNr
  52. // numbers aren't intended to be human readable, but it is easier to emit human readable integers
  53. out := make([]string, 0)
  54. out = append(out, fmt.Sprintf("%d", bands))
  55. for _, track := range sources {
  56. out = append(out, fmt.Sprintf("|%s|%d|", track.filename, len(track.beats)))
  57. if len(track.beats) <= 128 {
  58. for i := uint(0); i < bands; i += 1 {
  59. for j := uint(0); j < uint(len(track.beats)); j += 1 {
  60. // fmt.Fprintf(os.Stderr, "%d %d %f %f\n", i, j, track.beats[j].buckets[i].left, track.beats[j].buckets[i].right)
  61. }
  62. }
  63. }
  64. for _, beat := range track.beats {
  65. for _, band := range beat.buckets {
  66. l := math.Float64bits(band.left)
  67. r := math.Float64bits(band.right)
  68. var sb bytes.Buffer
  69. binary.Write(&sb, binary.BigEndian, l)
  70. binary.Write(&sb, binary.BigEndian, r)
  71. out = append(out, sb.String())
  72. }
  73. }
  74. }
  75. return out
  76. }
  77. func getfileinfo(filename string) (samplelength uint, channels uint) {
  78. getwd, _ := os.Getwd()
  79. p, err := exec.Run(soxpath, []string{"soxi", filename}, os.Environ(), getwd, exec.DevNull, exec.Pipe, exec.Pipe)
  80. if err != nil {
  81. panic(fmt.Sprintf("couldn't open soxi %s on file %s! %s", soxpath, filename, err))
  82. }
  83. soxierr, err := ioutil.ReadAll(p.Stderr)
  84. if err != nil {
  85. panic(fmt.Sprintf("Error reading soxi stderr %s", err))
  86. }
  87. if len(soxierr) > 0 {
  88. panic(fmt.Sprintf("soxi had stderr %s", soxierr))
  89. }
  90. soxiout := bufio.NewReader(p.Stdout)
  91. durexp := regexp.MustCompile("^Duration.* ([0-9]+) samples")
  92. chanexp := regexp.MustCompile("^Channels.* ([0-9]+)")
  93. for sampledone, channelsdone := false, false; !sampledone || !channelsdone; {
  94. line, err := soxiout.ReadString('\n')
  95. line = strings.TrimSpace(line)
  96. fmt.Println(line)
  97. if durexp.MatchString(line) {
  98. samplelength, err = strconv.Atoui(durexp.FindStringSubmatch(line)[1])
  99. sampledone = true
  100. } else if chanexp.MatchString(line) {
  101. channels, err = strconv.Atoui(chanexp.FindStringSubmatch(line)[1])
  102. channelsdone = true
  103. }
  104. if err != nil {
  105. panic(fmt.Sprintf("bad int returned from soxi! %s: %s", line, err))
  106. }
  107. }
  108. err = p.Close()
  109. if err != nil {
  110. panic(fmt.Sprintf("soxi returned err %s", err))
  111. }
  112. return samplelength, channels
  113. }
  114. // we need one goroutine per each band to read samples, and one goroutine to read from each channel
  115. // for each band
  116. // channels[i] = openband(file, i, width)
  117. func readinputlist(inputlist string) {
  118. in := false
  119. f, err := os.Open(inputlist, os.O_RDONLY, 0)
  120. if err != nil {
  121. panic(fmt.Sprintf("error reading inputlist %s\n", err))
  122. }
  123. b := bufio.NewReader(f)
  124. var s *source
  125. done := false
  126. for !done {
  127. line, err := b.ReadString('\n')
  128. if err != nil {
  129. if err == os.EOF {
  130. done = true
  131. } else {
  132. panic(err)
  133. }
  134. }
  135. line = strings.TrimSpace(line)
  136. fmt.Fprintln(os.Stderr, line)
  137. if strings.HasPrefix(line, "BEGIN ") {
  138. in = true
  139. s = new(source)
  140. s.filename = (strings.Split(line, " ", -1))[1]
  141. } else if line == "END" {
  142. in = false
  143. sources = append(sources, *s)
  144. } else if strings.HasPrefix(line, "LENGTH ") {
  145. lengthstr := (strings.Split(line, " ", -1))[1]
  146. length, err := strconv.Atoui(lengthstr)
  147. s.beats = make([]beat, length)
  148. if err != nil {
  149. panic(fmt.Sprintf("bad int in inputlist length %s: %s", lengthstr, err))
  150. }
  151. }
  152. }
  153. if in {
  154. panic(fmt.Sprintf("unfinished business reading input list", err))
  155. }
  156. }
  157. func (b buffer) empty() bool {
  158. return !((b.left != nil) || (b.right != nil))
  159. }
  160. func analyze(s *source) {
  161. fmt.Fprintf(os.Stderr, "starting file %s\n", s.filename)
  162. // tracks = tracks[:1+len(tracks)]
  163. // loop over bands
  164. for index := 0; index < len(s.beats); index++ {
  165. s.beats[index].buckets = make([]bucket, bands)
  166. }
  167. for band := uint(0); band < bands; band++ {
  168. i := uint(0)
  169. var bi uint
  170. samplelength, datachan := opensrcband(s.filename, band)
  171. beatlength := samplelength / uint(len(s.beats))
  172. fmt.Fprint(os.Stderr, "got channels\n")
  173. fmt.Fprintf(os.Stderr, "beatlength %d, band %d / %d, beats %d\n", beatlength, band, bands, len(s.beats))
  174. for {
  175. f := <-datachan
  176. if f.empty() && closed(datachan) {
  177. break
  178. }
  179. bi = 0
  180. // fmt.Println(samplelength, len(s.beats))
  181. dex := i / beatlength
  182. defer func() {
  183. e := recover()
  184. if e != nil {
  185. fmt.Println(bi, beatlength, dex, len(s.beats), len(f.left), len(f.right))
  186. panic(e)
  187. }
  188. }()
  189. for ; (dex < uint(len(s.beats)) && bi < beatlength) && bi < uint(len(f.left)); bi++ {
  190. b := bucket{float64(f.left[bi]), float64(f.right[bi])}
  191. if dex >= uint(len(s.beats)) {
  192. dex = uint(len(s.beats)) - 1
  193. // rolloff
  194. b.left = b.left * float64(dex/uint(len(s.beats)))
  195. b.right = b.right * float64(dex/uint(len(s.beats)))
  196. }
  197. s.beats[dex].buckets[band].left += math.Fabs(b.left)
  198. s.beats[dex].buckets[band].right += math.Fabs(b.right)
  199. // if i%10000 == 0 {
  200. fmt.Fprintf(os.Stderr, "%d %d %f %d %f %f %f\n", i, f.left[bi], float64(f.left[bi]), f.right[bi], float64(f.right[bi]), s.beats[dex].buckets[band].left, s.beats[dex].buckets[band].right)
  201. // }
  202. i++
  203. }
  204. }
  205. }
  206. }
  207. func opensrcband(filename string, band uint) (uint, <-chan buffer) {
  208. bandwidth := samplerate / 2 / bands
  209. bandlow := band * bandwidth
  210. bandhigh := bandlow + bandwidth
  211. samplelength, channels := getfileinfo(filename)
  212. currsoxopts := make([]string, 0)
  213. currsoxopts = append(currsoxopts, "sox")
  214. currsoxopts = append(currsoxopts, filename)
  215. currsoxopts = append(currsoxopts, soxformatopts...)
  216. currsoxopts = append(currsoxopts, "-")
  217. if channels == 1 {
  218. currsoxopts = append(currsoxopts, []string{"remix", "1", "1"}...)
  219. // stereo is a noop
  220. // everything >2 channels doesn't have enough information so I am assuming the layout based on mpeg standards
  221. } else if channels == 3 {
  222. currsoxopts = append(currsoxopts, []string{"remix", "1,3", "2,3"}...)
  223. } else if channels == 4 {
  224. currsoxopts = append(currsoxopts, []string{"remix", "1,3,4", "2,3,4"}...)
  225. } else if channels == 5 {
  226. currsoxopts = append(currsoxopts, []string{"remix", "1,3,4", "2,3,5"}...)
  227. } else if channels == 6 { // 5.1
  228. currsoxopts = append(currsoxopts, []string{"remix", "1,3,4,5", "2,3,4,6"}...)
  229. } else if channels == 7 { // 6.1
  230. currsoxopts = append(currsoxopts, []string{"remix", "1,3,4,5,7", "2,3,4,6,7"}...)
  231. } else if channels == 8 { // 7.1
  232. currsoxopts = append(currsoxopts, []string{"remix", "1,3,4,5,7", "2,3,4,6,8"}...)
  233. } else if channels > 8 { // no idea, just take first two
  234. currsoxopts = append(currsoxopts, []string{"remix", "1", "2"}...)
  235. }
  236. currsoxopts = append(currsoxopts, "sinc")
  237. fmt.Println(bandhigh, samplerate/2/bands)
  238. if bandhigh >= samplerate/2/bands {
  239. currsoxopts = append(currsoxopts, strconv.Uitoa(bandlow))
  240. } else {
  241. currsoxopts = append(currsoxopts, strconv.Uitoa(bandlow)+"-"+strconv.Uitoa(bandhigh))
  242. }
  243. currsoxopts = append(currsoxopts, []string{"channels", "2"}...)
  244. fmt.Fprintln(os.Stderr, strings.Join(currsoxopts, " "))
  245. cmd := startsox(soxpath, currsoxopts, true)
  246. // some day this will use libsox
  247. datachan := make(chan buffer, 5)
  248. go func() {
  249. OUTER:
  250. for {
  251. var frame buffer
  252. frame.left = make([]int16, buffersize)
  253. frame.right = make([]int16, buffersize)
  254. for i := uint(0); i < buffersize; i++ {
  255. err := binary.Read(cmd.Stdout, binary.BigEndian, &(frame.left[i]))
  256. if err != nil {
  257. if err != os.EOF {
  258. panic(err)
  259. } else {
  260. fmt.Fprintf(os.Stderr, "Done reading file at L %d, closing datachan\n", i)
  261. frame.left = frame.left[0:i]
  262. frame.right = frame.right[0:i]
  263. datachan <- frame
  264. close(datachan)
  265. break OUTER
  266. }
  267. }
  268. err = binary.Read(cmd.Stdout, binary.BigEndian, &(frame.right[i]))
  269. if err != nil {
  270. if err != os.EOF {
  271. panic(err)
  272. } else {
  273. fmt.Fprintf(os.Stderr, "Done reading file R %d, closing datachan\n", i)
  274. frame.left = frame.left[0:i]
  275. frame.right = frame.right[0:i]
  276. datachan <- frame
  277. close(datachan)
  278. break OUTER
  279. }
  280. }
  281. fmt.Fprintln(os.Stderr, i, frame.left[i], frame.right[i])
  282. }
  283. datachan <- frame
  284. if closed(datachan) {
  285. fmt.Fprintln(os.Stderr, "Output channel closed, returning")
  286. break
  287. }
  288. }
  289. cmd.Close()
  290. close(datachan)
  291. }()
  292. return samplelength, datachan
  293. }
  294. func startsox(sox string, currsoxopts []string, outpipe bool) *exec.Cmd {
  295. getwd, _ := os.Getwd()
  296. outstat := exec.Pipe
  297. if !outpipe {
  298. outstat = exec.PassThrough
  299. }
  300. fmt.Println(currsoxopts)
  301. p, err := exec.Run(sox, currsoxopts, os.Environ(), getwd, exec.DevNull, outstat, exec.PassThrough)
  302. if err != nil {
  303. panic(fmt.Sprintf("couldn't open band for reason %s", err))
  304. }
  305. fmt.Fprintf(os.Stderr, "sox pid is %d\n", p.Pid)
  306. return p
  307. }
  308. func readflags() *string {
  309. sourcelist := flag.String("sourcelist", "sourcelist.txt", "list of source files with metadata")
  310. flag.UintVar(&bands, "bands", 10, "number of bands")
  311. var err os.Error
  312. soxpath, err = exec.LookPath("sox")
  313. defaults := "Default is " + soxpath
  314. checksoxpath := false
  315. if err != nil {
  316. checksoxpath = true
  317. defaults = "No sox found in path. No default"
  318. }
  319. flag.StringVar(&soxpath, "sox", soxpath, "Path to sox binary. "+defaults)
  320. flag.UintVar(&samplerate, "samplerate", 44100, "Sample rate in hz. Default 44100")
  321. flag.UintVar(&beatlength, "beatlength", 0, "Length of output beats in samples. No default")
  322. flag.StringVar(&tmpdir, "tmpdir", "/tmp", "Tmpdir to hold FIFOs. Must exist.")
  323. flag.StringVar(&outputdir, "outputdir", ".", "Dir to hold output files. Default is working directory")
  324. flag.StringVar(&outputext, "outputext", "remix.wav", "Output default extension. Do NOT start this with a dot. Default is remix.wav")
  325. flag.Parse()
  326. if checksoxpath && soxpath == "" {
  327. fmt.Fprintln(os.Stderr, "No sox found on PATH and no sox specified")
  328. }
  329. if beatlength == 0 {
  330. fmt.Fprintln(os.Stderr, "No beatlength specified.")
  331. os.Exit(1)
  332. }
  333. if samplerate == 0 && !(samplerate == 22050 || samplerate == 44100 || samplerate == 48000) {
  334. fmt.Fprintln(os.Stderr, "Bad samplerate specified")
  335. os.Exit(1)
  336. }
  337. stat, err := os.Stat(tmpdir)
  338. if err != nil || !stat.IsDirectory() {
  339. fmt.Fprintf(os.Stderr, "tmpdir %s does not exist or is not a directory: %s.\n", tmpdir, err.String())
  340. os.Exit(1)
  341. }
  342. stat, err = os.Stat(outputdir)
  343. if err != nil || !stat.IsDirectory() {
  344. fmt.Fprintf(os.Stderr, "outputdir %s does not exist or is not a directory: %s.\n", outputdir, err.String())
  345. os.Exit(1)
  346. }
  347. soxformatopts = append(soxformatopts, []string{"-b", "16", "-e", "signed-integer", "-B", "-r", strconv.Uitoa(samplerate), "-t", "raw"}...)
  348. buffersize = 512
  349. return sourcelist
  350. }
  351. func main() {
  352. sourcelist := readflags()
  353. readinputlist(*sourcelist)
  354. // phase 1, analyze all sources
  355. for i, _ := range sources {
  356. analyze(&sources[i])
  357. fmt.Fprintln(os.Stderr, "done analyze")
  358. generate(&sources[i])
  359. }
  360. }
  361. func generate(s *source) {
  362. fmt.Println("generate")
  363. outfilename := s.filename + "." + outputext
  364. basename := path.Base(outfilename)
  365. outfilename = path.Join(outputdir, basename)
  366. fifos := make([]string, 0, bands)
  367. for i := uint(0); i < bands; i++ {
  368. // make a fifo
  369. fifoname := path.Join(tmpdir, basename+strconv.Uitoa(i))
  370. fmt.Fprintf(os.Stderr, "making fifo %d\n", i)
  371. err := os.RemoveAll(fifoname)
  372. if err != nil {
  373. panic(err)
  374. }
  375. errno := syscall.Mkfifo(fifoname, 0600)
  376. if errno != 0 {
  377. panic(os.NewSyscallError("Mkfifo", errno))
  378. }
  379. fifos = append(fifos, fifoname)
  380. // make a goroutine to read from the channel with processinputband and write to the fifo
  381. // open the fifo inside the goroutine so the call to open doesn't block the main thread
  382. go processinputband(s, fifoname, i, openinputband(i))
  383. }
  384. // start sox with all the fifos
  385. // bands is number of files to read from
  386. // +1 output file
  387. // +1 for "-m"
  388. // +1 for "sox" at start
  389. opts := make([]string, 0, uint(len(soxformatopts))+bands+3)
  390. opts = append(opts, "sox", "-m")
  391. for _, f := range fifos {
  392. opts = append(opts, soxformatopts...)
  393. opts = append(opts, f)
  394. }
  395. opts = append(opts, outfilename)
  396. cmd := startsox(soxpath, opts, false)
  397. msg, err := cmd.Wait(0)
  398. if err != nil {
  399. panic(err)
  400. }
  401. fmt.Println(msg.String())
  402. }
  403. func openinputband(band uint) <-chan buffer {
  404. sfile := flag.Arg(rand.Intn(flag.NArg() - 1))
  405. outchan := make(chan buffer, 5)
  406. go func() {
  407. for {
  408. _, inchan := opensrcband(sfile, band)
  409. for {
  410. buffer := <-inchan
  411. if closed(inchan) || closed(outchan) {
  412. break
  413. }
  414. outchan <- buffer
  415. }
  416. if closed(outchan) {
  417. close(inchan)
  418. break
  419. }
  420. }
  421. }()
  422. return outchan
  423. }
  424. func processinputband(s *source, fifoname string, band uint, channel <-chan buffer) {
  425. fifo, err := os.Open(fifoname, os.O_WRONLY, 0)
  426. if err != nil {
  427. panic(err)
  428. }
  429. defer func() {
  430. eek := recover()
  431. err := fifo.Close()
  432. if err != nil {
  433. panic(err)
  434. }
  435. if eek != nil {
  436. panic(eek)
  437. }
  438. }()
  439. origbeats := s.beats
  440. for _, b := range s.beats {
  441. fmt.Println(b)
  442. }
  443. buckets := make([]bucket, len(s.beats))
  444. buffers := make([]buffer, len(s.beats))
  445. for i := 0; i < len(buffers); i++ {
  446. buffers[i].left = make([]int16, beatlength)
  447. buffers[i].right = make([]int16, beatlength)
  448. }
  449. inbuf := <-channel
  450. inbufpos := 0
  451. beatpos := uint(0)
  452. done := false
  453. for !done {
  454. beat := beatpos / beatlength
  455. buffpos := beatpos % beatlength
  456. if buckets[beat].left < origbeats[beat].buckets[band].left && buckets[beat].right < origbeats[beat].buckets[band].right {
  457. buffers[beat].left[buffpos] += inbuf.left[inbufpos]
  458. buckets[beat].left += float64(inbuf.left[inbufpos])
  459. buffers[beat].right[buffpos] += inbuf.right[inbufpos]
  460. buckets[beat].right += float64(inbuf.right[inbufpos])
  461. inbufpos++
  462. }
  463. if uint(inbufpos) >= beatlength {
  464. inbufpos = 0
  465. inbuf = <-channel
  466. }
  467. beatpos++
  468. if beatpos >= beatlength {
  469. beatpos = 0
  470. }
  471. done = true
  472. for _, b := range buckets {
  473. if !(b.left < origbeats[beat].buckets[band].left && b.right < origbeats[beat].buckets[band].right) {
  474. done = false
  475. break
  476. }
  477. }
  478. }
  479. for _, b := range buffers {
  480. binary.Write(fifo, binary.BigEndian, b.left)
  481. binary.Write(fifo, binary.BigEndian, b.right)
  482. }
  483. }