├── README.md ├── LICENSE └── longwave.py /README.md: -------------------------------------------------------------------------------- 1 | # micropython-longwave 2 | WAV player for MicroPython board 3 | 4 | Uses double buffered approach to load chunks of samples. 5 | Needs to be polled frequently so the chunks can be refreshed. 6 | 7 | Matt Page / mattmatic@hotmail.com 8 | 9 | # Usage 10 | from longwave import LongWave 11 | lw = LongWave() # Default uses DAC1 and Timer4 12 | lw.play('hisnibs.wav') 13 | 14 | lw.play('hisnibs.wav', 120) # 120% speed 15 | 16 | (Make sure you call lw.poll() at least every 100ms) 17 | 18 | # History 19 | 2015-04-26 Initial upload 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Matt Page / MattMatic 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /longwave.py: -------------------------------------------------------------------------------- 1 | """ 2 | Stuff to play long WAVE files. 3 | 4 | 2015-04-22 Matt Page 5 | 2015-04-26 Added replay ability 6 | 7 | Double buffered, using a timer to reload the DAC and a poll function to 8 | fill up the queue. 9 | Works well with 200ms of audio, and polled at about 100ms 10 | 11 | """ 12 | 13 | import wave 14 | import pyb 15 | from pyb import DAC 16 | 17 | class LongWave: 18 | """ 19 | Callback for the timer. 20 | Switches buffers, and deactivates callback when nothing to do. 21 | Toggles should be interrupt-safe 22 | """ 23 | def timer_callback(self, timer): 24 | self._have[self._doneindex]=False 25 | self._doneindex = (self._doneindex+1)%2 26 | if (self._have[self._doneindex]): 27 | self._dac.write_timed(self._buf[self._doneindex], self._rate) 28 | else: 29 | self._doneindex=0 30 | timer.callback(None) 31 | self._running = False 32 | 33 | """ 34 | Class variables: 35 | _f = wave file from wave.open 36 | _speed = Percent playback speed 37 | _rate = samples per second 38 | _amount = number of samples to take per chunk 39 | _freq = frequency of chunk switchover (4 Hertz) 40 | _dac = DAC object 41 | _have = whether we have chunks 1 and/or 2 42 | _buf = the buffers of samples 43 | _doneindex = which chunk (0 or 1) that's been completed 44 | _timer = timer object 45 | _running = whether the timer is running 46 | """ 47 | def __init__(self, dac=DAC(1), timerno=4): 48 | global lwr 49 | lwr = self 50 | self._f = None 51 | self._rate = 0 52 | self._amount = 0 53 | self._freq = 4 # in Hertz 54 | self._dac = dac 55 | self._have = [False,False] 56 | self._buf = [None,None] 57 | self._doneindex = 0 58 | self._speed = 100 59 | self._timer = pyb.Timer(timerno, freq=self._freq) 60 | self._running = False 61 | self._fill() 62 | 63 | def __del__(self): 64 | self._timer.callback(None) 65 | self._f.close() 66 | 67 | def _read(self): 68 | return self._f.readframes(self._amount) 69 | 70 | """ 71 | Fill one or two buffers if needed. 72 | If timer callback is not running, then kick off the timer again 73 | """ 74 | def _fillbuf(self, which=0): 75 | if (not self._have[which]): 76 | self._buf[which] = self._read() 77 | self._have[which] = True 78 | 79 | def _fill(self): 80 | if (not self._f): 81 | return 82 | 83 | if (not self._have[0]) and (not self._have[1]): 84 | self._doneindex = 0 85 | 86 | self._fillbuf(0) 87 | self._fillbuf(1) 88 | 89 | if (not self._running): 90 | self._dac.write_timed(self._buf[0], self._rate) 91 | self._timer.callback(self.timer_callback) 92 | self._running = True 93 | 94 | """ 95 | Call this regularly (faster than the buffer rate of 5Hz) 96 | """ 97 | def poll(self): 98 | self._fill() 99 | 100 | def play(self, filename, speed=100): 101 | if (self._f): 102 | self._f.close() 103 | #print('Playing ',filename) 104 | self._have[0] = False 105 | self._have[1] = False 106 | self._running = False 107 | self._speed = speed 108 | self._f = None 109 | self._f = wave.open(filename) 110 | self._rate = (self._f.getframerate() * self._speed) // 100 111 | self._amount = self._rate // self._freq 112 | self._fill() 113 | 114 | --------------------------------------------------------------------------------