-
Notifications
You must be signed in to change notification settings - Fork 0
/
wavePlayer.py
305 lines (250 loc) · 9.28 KB
/
wavePlayer.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
'''
(c) 2021 Daniel Perron
MIT License
example of audio output using PWM and DMA
right now it works only with wave file at
8000 sample rate , stereo or mono, and 16 bits audio
GPIO 2 & 3 pin 4 and 5 are the output
You need to use headphones with a 1K resistor in series on
left and right speaker
The myPWM subclass sets the maximum count to 255 at a frequency around 122.5KHz.
The myDMA class allows to use direct memory access to transfer each frame at the current sample rate
You need to install the wave.py and chunk.py from
https://github.com/joeky888/awesome-micropython-lib/tree/master/Audio
SDCard.py is available in https://github.com/micropython/micropython/tree/master/drivers/sdcard
please be sure to rename it SDCard.py into the pico lib folder
*** be sure to increase the SPI clock speed > 5MHz
*** once SDCard is initialize set the spi to an higher clock
How it works,
1 - We set the PWM to a range of 255, 1023 for 10 bits, at 122Khz
2 - We read the wave file using the class wave which will set the sample rate and read the audio data by chunk
3 - Mono files are converted to stereo by duplicating the original audio samples
4 - Each chunk are converted to 16 bit signed to unsigned char with the middle at 128
5 - Wait for the DMA to be completed. On first it will be anyway.
6 - The converted chunk is then passed to the DMA to be transfered at the sample rate using one of built-in timer
7 - Go on step 2 until is done.
P.S. use rshell to transfer wave files to the Pico file system
April 20
Version 0.1
--- Add DMA chainning. This removes the glitch betweem DMA transfer
--- assembly function convert2PWM replace the struct pack and compack
since it is not necessary to convert the binary string it is way faster.
Version 0.2
--- Add mono audio file handling
For Headphones
2K
PIO2 -/\/\/-----+----- headphone left
|
=== 0.1uF
|
PIO4 -----------+----- headphone ground
|
=== 0.1uF
2k |
PIO3 -/\/\/-----+----- headphone right
For amplifier don't use PIO4 and the capacitor should be 2200pF and connected to GND.
'''
import wave
import uctypes
from myDMA import myDMA
from myPWM import myPWM
from machine import Pin
#r0 buffer address
#r1 number of word to do
#r2 8 or 10 bit PWM
#then r2 hold data reference by r0
#r3 = 32768 to convert (-32768..32767) to (0..65535)
#r4 hold 255 or 1023 (8 or 10 bits)
#r5 hold /64 or /256 ( 6 or 8 bit shift)
@micropython.asm_thumb
def convert2PWM(r0,r1,r2):
#r3=32768
mov(r3,1)
mov(r4,15)
lsl(r3,r4)
# 8bits or 10 bit PWM
mov(r4,255)
cmp(r2,10)
bne(PWM8BITS)
#ok we are 10 bits
# set r4 for 1023
lsl(r4,r4,2)
add(r4,r4,3)
mov(r5,6)
b(loop)
label(PWM8BITS)
#ok then this is 8 bits
#r4 already to 255
mov(r5,8)
label(loop)
# get 16 bit data
ldrh(r2,[r0,0])
# add 32768
add(r2,r2,r3)
# shift right 6 bit or 8 bit
lsr(r2,r5)
# and 255 or 1023
and_(r2,r4)
# store new data
strh(r2,[r0,0])
add(r0,2)
sub(r1,1)
bgt(loop)
pass
@micropython.asm_thumb
def interleavebytes(r0,r1,r2):
#r0 MONO wav audio buffer address
#r1 Stereo wav audio ouput buffer address
#r2 number of halfwords (16 bit audio samples) from MONO buffer to convert
label(loop)
# get 16 bit data
ldrh(r3,[r0,0])
# copy over
strh(r3,[r1,0])
add(r1,2)
# and duplicate for second channel
strh(r3,[r1,0])
# point to next audio data halfword
add(r1,2)
add(r0,2)
# decrement counter and loop unless zero
sub(r2,1)
bgt(loop)
class wavePlayer:
def __init__(self,leftPin=Pin(5),rightPin=Pin(6), virtualGndPin=Pin(7), # was 2, 3, 4
dma0Channel=10,dma1Channel=11,dmaTimer=3,pwmBits=10):
#left channel Pin needs to be an even GPIO Pin number
#right channel Pin needs to be left channel + 1
self.pwmBits=pwmBits
self.PWM_DIVIDER = 1
if self.pwmBits == 10:
self.PWM_TOP = 1023
self.PWM_HALF = 512
else:
# 8 bits
self.PWM_TOP = 255
self.PWM_HALF = 128
self.leftPin=leftPin
self.rightPin=rightPin
self.virtualGndPin=virtualGndPin
# set PWM
self.leftPWM=myPWM(leftPin,divider=self.PWM_DIVIDER,top=self.PWM_TOP)
self.leftPWM.duty(self.PWM_HALF)
self.rightPWM=myPWM(rightPin,divider=self.PWM_DIVIDER,top=self.PWM_TOP)
self.rightPWM.duty(self.PWM_HALF)
if not (self.virtualGndPin is None):
self.virtualGndPWM=myPWM(self.virtualGndPin,divider=self.PWM_DIVIDER,top=self.PWM_TOP)
self.virtualGndPWM.duty(self.PWM_HALF)
# set DMA channel
self.dma0Channel = dma0Channel
self.dma1Channel = dma1Channel
self.dmaTimer = dmaTimer
def stop(self):
self.dma0.abort()
self.dma1.abort()
def play(self,filename):
# open Audio file and get information
f = wave.open(filename,'rb')
rate = f.getframerate()
bytesDepth = f.getsampwidth()
channels = f.getnchannels()
frameCount = f.getnframes()
# set number of Frames/chunk and DMAunitSize for Stereo samples
DMAunitSize = 4
nbFrame = 2048
# adjust down if 1 channel (mono)
if channels == 1:
nbFrame=1024
#number of 16bit audio chunks per Frame
nbData = nbFrame*2
# Set DMA channel and timer rate
# the divider set the rate at 2Khz (125Mhz//62500)
# The multiplier use the sample rate to adjust it correctly
if rate == 44100:
self.dma0 = myDMA(self.dma0Channel,timer=self.dmaTimer,clock_MUL= 15, clock_DIV=42517)
else:
self.dma0 = myDMA(self.dma0Channel,timer=self.dmaTimer,clock_MUL= rate // 2000, clock_DIV=62500)
self.dma1 = myDMA(self.dma1Channel,timer=self.dmaTimer) # don't need to set timer clock
#setup DMA chain dma0 to dma1 and vice versa
self.dma0.setCtrl(src_inc=True, dst_inc=False,data_size=DMAunitSize,chainTo=self.dma1.channel)
self.dma1.setCtrl(src_inc=True, dst_inc=False,data_size=DMAunitSize,chainTo=self.dma0.channel)
# need to alternate DMA buffer using a toggle flag
toggle = True
# need to start first frame
First = True
# loop until is done
frameLeft = frameCount
while frameLeft>0:
# first DMA
if frameLeft < nbFrame:
nbFrame = frameLeft
nbData = nbFrame*2
if toggle:
t1 = f.readframes(nbFrame)
#--- Duplicate mono audio samples to simulate stereo sound (on both channels)
if channels ==1:
t3 = bytearray(4096)
interleavebytes(uctypes.addressof(t1),uctypes.addressof(t3),nbFrame)
t1=t3
#--- make t1 stereo data to PWM compatible
convert2PWM(uctypes.addressof(t1), nbData,self.pwmBits)
self.dma1.move(uctypes.addressof(t1),self.leftPWM.PWM_CC,nbFrame*DMAunitSize)
# check if previous DMA is done
while self.dma0.isBusy():
pass
# start DMA.
# Since they are chained we need to start the first DMA
if First:
self.dma1.start()
First = False
else:
t0 = f.readframes(nbFrame)
#--- Duplicate mono audio samples to simulate stereo sound (on both channels)
if channels == 1:
t3 = bytearray(4096)
interleavebytes(uctypes.addressof(t0),uctypes.addressof(t3),nbFrame)
t0=t3
#--- make t0 stereo data to PWM compatible
convert2PWM(uctypes.addressof(t0), nbData,self.pwmBits)
self.dma0.move(uctypes.addressof(t0),self.leftPWM.PWM_CC,nbFrame*DMAunitSize)
# check if previous DMA is done
while self.dma1.isBusy():
pass
toggle = not toggle
frameLeft -= nbFrame
if toggle:
self.dma1.pause()
while self.dma0.isBusy():
pass
self.dma0.pause()
else:
self.dma0.pause()
while self.dma1.isBusy():
pass
self.dma1.pause()
f.close()
self.stop()
if __name__ == "__main__":
import uos
import SDCard
# mount SDCard
from machine import SPI,Pin
sd = SDCard.SDCard(SPI(1),Pin(13))
#need to pump up the SPI clock rate
# below 3MHz it won't work!
sd.init_spi(50_000_000)
uos.mount(sd,"/sd")
player = wavePlayer()
waveFolder= "/sd/Wendy"
wavelist = []
for i in uos.listdir(waveFolder):
if i.find(".wav")>=0:
wavelist.append(waveFolder+"/"+i)
elif i.find(".WAV")>=0:
wavelist.append(waveFolder+"/"+i)
try:
for i in wavelist:
print(i)
player.play(i)
except KeyboardInterrupt:
player.stop()