Mozzi  version 2016-12-11-17:03
sound synthesis library for Arduino
audio2huff.py
1 #! /usr/bin/python
2 #
3 # Generator for compressed Arduino audio data
4 # Thomas Grill, 2011
5 # http://grrrr.org
6 #
7 # Modified by TIm Barrass 2013
8 # - changed PROGMEM to __attribute__((section(".progmem.data"))), to stop compiler warning
9 # - moved huffman table to progmem
10 # - added --name argument to give all constants specific names
11 # - changed all constant names to upper case
12 # - added include guards, Arduino and avr includes
13 #
14 # Dependencies:
15 # Numerical Python (numpy): http://numpy.scipy.org/
16 # scikits.audiolab: http://pypi.python.org/pypi/scikits.audiolab/
17 # purehuff: http://grrrr.org/purehuff
18 # pylab / matplotlib (only for plotting): http://matplotlib.sourceforge.net/
19 #
20 # For help on options invoke with:
21 # audio2huff --help
22 
23 import sys,os.path
24 from itertools import imap,chain,izip
25 
26 try:
27  import numpy as N
28 except ImportError:
29  print >>sys.stderr, "Error: Numerical Python not found"
30  exit(-1)
31 
32 try:
33  from scikits.audiolab import Sndfile
34 except ImportError:
35  print >>sys.stderr, "Error: Scikits.audiolab not found"
36  exit(-1)
37 
38 try:
39  import purehuff
40 except ImportError:
41  print >>sys.stderr, "Error: purehuff module not found"
42  exit(-1)
43 
44 
45 def grouper(n,seq):
46  """group list elements"""
47  it = iter(seq)
48  while True:
49  l = [v for _,v in izip(xrange(n),it)]
50  if l:
51  yield l
52  if len(l) < n:
53  break
54 
55 def arrayformatter(seq,perline=40):
56  """format list output linewise"""
57  return ",\n".join(",".join(imap(str,s)) for s in grouper(perline,seq))
58 
59 if __name__ == "__main__":
60  from optparse import OptionParser
61  parser = OptionParser()
62  parser.add_option("--bits", type="int", default=8, dest="bits",help="bit resolution")
63  parser.add_option("--sndfile", dest="sndfile",help="input sound file")
64  parser.add_option("--hdrfile", dest="hdrfile",help="output C header file")
65  parser.add_option("--name", dest="name",help="prefix for tables and constants in file")
66  parser.add_option("--plothist", type="int", default=0, dest="plothist",help="plot histogram")
67  (options, args) = parser.parse_args()
68 
69  if not options.sndfile:
70  print >>sys.stderr,"Error: --sndfile argument required"
71  exit(-1)
72 
73  sndf = Sndfile(options.sndfile,'r')
74  sound = sndf.read_frames(sndf.nframes)
75  fs = sndf.samplerate
76  del sndf
77 
78  # mix down multi-channel audio
79  if len(sound.shape) > 1:
80  sound = N.mean(sound,axis=1)
81 
82  # convert to n bits (no dithering, except it has already been done with the same bit resolution for the soundfile)
83  sound8 = N.clip((sound*(2**(options.bits-1))).astype(int),-2**(options.bits-1),2**(options.bits-1)-1)
84  # the following mapping with int is necessary as numpy.int32 types are not digested well by the HuffmanTree class
85  dsound8 = map(int,chain((sound8[0],),imap(lambda x: x[1]-x[0],izip(sound8[:-1],sound8[1:]))))
86 
87  print >>sys.stderr,"min/max: %i/%i"%(N.min(sound8),N.max(sound8))
88  print >>sys.stderr,"data bits: %i"%(len(sound8)*options.bits)
89 
90  hist = purehuff.histogram(dsound8)
91 
92  if options.plothist:
93  try:
94  import pylab as P
95  except ImportError:
96  print >>sys.stderr, "Plotting needs pylab"
97 
98  from collections import defaultdict
99  d = defaultdict(float)
100  for n,v in hist:
101  d[v] += n
102  x = range(min(d.iterkeys()),max(d.iterkeys())+1)
103  y = [d[xi] for xi in x]
104 
105  P.title("Histogram of sample differentials, file %s"%os.path.split(options.sndfile)[-1])
106  P.plot(x,y,marker='x')
107  P.show()
108 
109  hufftree = purehuff.HuffTree(hist)
110 
111  # get decoder instance
112  decoder = hufftree.decoder()
113  # get encoder instance
114  encoder = hufftree.encoder()
115  # encode data
116  enc = encoder(dsound8)
117 
118  print >>sys.stderr,"encoded bits: %i"%len(enc)
119  print >>sys.stderr,"ratio: %.0f%%"%((len(enc)*100.)/(len(sound8)*8))
120  print >>sys.stderr,"decoder length: %.0f words"%(len(decoder.huff))
121 
122  if options.hdrfile:
123  hdrf = file(options.hdrfile,'wt')
124  print >>hdrf,"// generated by Mozzi/extras/python/audio2huff.py \n"
125  print >>hdrf,"#ifndef " + options.name + "_H_"
126  print >>hdrf,"#define " + options.name + "_H_\n"
127  print >>hdrf,'#if ARDUINO >= 100'
128  print >>hdrf,'#include "Arduino.h"'
129  print >>hdrf,'#else'
130  print >>hdrf,'#include "WProgram.h"'
131  print >>hdrf,'#endif \n'
132  print >>hdrf,'#include <avr/pgmspace.h>\n \n'
133  print >>hdrf,"#define " + options.name + "_SAMPLERATE %i"%fs
134  print >>hdrf,"#define " + options.name + "_SAMPLE_BITS %i"%options.bits
135  print >>hdrf,'int const __attribute__((section(".progmem.data"))) ' + options.name + '_HUFFMAN[%i] = {\n%s\n};'%(len(decoder.huff),arrayformatter(decoder.huff))
136  print >>hdrf,'unsigned long const ' + options.name + '_SOUNDDATA_BITS = %iL;'%len(enc)
137  print >>hdrf,'unsigned char const __attribute__((section(".progmem.data"))) ' + options.name + '_SOUNDDATA[] = {\n%s\n};'%arrayformatter(enc.data)
138  print >>hdrf,"#endif /* " + options.name + "_H_ */"