1

I'm building a super barebones game console with a rasPi zero. I'm trying to get it to play some rudimentary music - I'd like to be able to write little tunes programatically (eventually have the games generate the music perhaps).

I have connected a little piezo buzzer to PWM pin 12 and can make it play some notes with this little python program:

import RPi.GPIO as GPIO 
import time 

GPIO.setmode(GPIO.BCM)
GPIO.setup(12, GPIO.OUT)
p = GPIO.PWM(12, 100)

c4 = 261
d4 = 294
e4 = 329
f4 = 349
g4 = 392
a4 = 440
b4 = 493
c5 = 523.25

speed = 0.1

GPIO.output(12, True) 
p.start(10) # 10% duty cycle sounds 'ok'

p.ChangeFrequency(c4)
time.sleep(speed)
p.ChangeFrequency(d4)  
time.sleep(speed)
p.ChangeFrequency(e4)   
time.sleep(speed)
p.ChangeFrequency(f4)  
time.sleep(speed)
p.ChangeFrequency(g4)    
time.sleep(speed)
p.ChangeFrequency(a4)    
time.sleep(speed)
p.ChangeFrequency(b4)    
time.sleep(speed)
p.ChangeFrequency(c5)    
time.sleep(speed)

p.stop()
GPIO.cleanup()

which definitely gives me a starting point, however, I'd really like to be able to have more than 1 voice - maybe 4: enough for a simple drum beat and bass line.

I'm at a loss as to how to combine multiple 'instruments' with this set up.

I did some research which enabled me to write a program that attempts to do some wavetable synthesis but all I got was a very high pitched modem squeak (I converted the article's sine wave data into duty cycle percentages):

import RPi.GPIO as GPIO 
import time 

GPIO.setmode(GPIO.BCM)
GPIO.setup(12, GPIO.OUT) 
p = GPIO.PWM(12, 100)

c4 = 261

wave = [
    50.20, 51.37, 52.55, 53.73, 54.90, 56.08, 57.25, 58.43, 
    59.61, 60.78, 61.96, 63.14, 64.31, 65.49, 66.67, 67.84, 
    69.02, 70.20, 71.37, 72.55, 73.33, 74.51, 75.69, 76.47, 
    77.65, 78.82, 79.61, 80.78, 81.57, 82.35, 83.53, 84.31, 
    85.10, 85.88, 87.06, 87.84, 88.63, 89.41, 90.20, 90.59, 
    91.37, 92.16, 92.55, 93.33, 94.12, 94.51, 94.90, 95.69, 
    96.08, 96.47, 96.86, 97.25, 97.65, 98.04, 98.43, 98.43, 
    98.82, 99.22, 99.22, 99.61, 99.61, 99.61, 99.61, 99.61, 
    100.00, 99.61, 99.61, 99.61, 99.61, 99.61, 99.22, 99.22, 
    98.82, 98.43, 98.43, 98.04, 97.65, 97.25, 96.86, 96.47, 
    96.08, 95.69, 94.90, 94.51, 94.12, 93.33, 92.55, 92.16, 
    91.37, 90.59, 90.20, 89.41, 88.63, 87.84, 87.06, 85.88, 
    85.10, 84.31, 83.53, 82.35, 81.57, 80.78, 79.61, 78.82, 
    77.65, 76.47, 75.69, 74.51, 73.33, 72.55, 71.37, 70.20, 
    69.02, 67.84, 66.67, 65.49, 64.31, 63.14, 61.96, 60.78, 
    59.61, 58.43, 57.25, 56.08, 54.90, 53.73, 52.55, 51.37, 
    50.20, 49.02, 47.84, 46.67, 45.49, 44.31, 43.14, 41.96, 
    40.78, 39.61, 38.43, 37.25, 36.08, 34.90, 33.73, 32.55, 
    31.37, 30.20, 29.02, 27.84, 27.06, 25.88, 24.71, 23.92, 
    22.75, 21.57, 20.78, 19.61, 18.82, 18.04, 16.86, 16.08, 
    15.29, 14.51, 13.33, 12.55, 11.76, 10.98, 10.20, 9.80, 
    9.02, 8.24, 7.84, 7.06, 6.27, 5.88, 5.49, 4.71, 4.31, 
    3.92, 3.53, 3.14, 2.75, 2.35, 1.96, 1.96, 1.57, 1.18, 
    1.18, 0.78, 0.78, 0.78, 0.78, 0.78, 0.39, 0.78, 0.78, 
    0.78, 0.78, 0.78, 1.18, 1.18, 1.57, 1.96, 1.96, 2.35, 
    2.75, 3.14, 3.53, 3.92, 4.31, 4.71, 5.49, 5.88, 6.27, 
    7.06, 7.84, 8.24, 9.02, 9.80, 10.20, 10.98, 11.76, 
    12.55, 13.33, 14.51, 15.29, 16.08, 16.86, 18.04, 18.82, 
    19.61, 20.78, 21.57, 22.75, 23.92, 24.71, 25.88, 27.06, 
    27.84, 29.02, 30.20, 31.37, 32.55, 33.73, 34.90, 36.08, 
    37.25, 38.43, 39.61, 40.78, 41.96, 43.14, 44.31, 45.49, 
    46.67, 47.84, 49.02, ]
freq = len(wave)

GPIO.output(12, True)
p.start(0)
p.ChangeFrequency(16000)
try:
    while True:
      for value in wave:
        p.ChangeDutyCycle(value)
        time.sleep(1/(c4*freq))

except:
  p.stop()
  GPIO.cleanup()

I'm well beyond my ability with regards to sound theory, but I guess this is as good a place to start as any. I've found a lot of arduino based resources which all appear to use various timers which pushes my limited understanding of C makes it harder to translate them into rasperrypython. I really do only want very simple tunes (and maybe the odd sound effect?).

How can I use python to generate 4-voice chip tunes over PWM?

UPDATE: so I found an article that explains the whole wavetable thing a little clearer (for my brain anyway), so I guess my question becomes - how do I control the PWM and update the duty cycle with enough precision using python?

  • What's the question? – Brick Mar 18 '18 at 13:37
  • How can I generate 4-voice chip tunes with python and PWM? – MalphasWats Mar 18 '18 at 13:46
  • One of the problems you are running in to is trying to do real-time hardware control on a non-real-time system. – John Keates Mar 20 '18 at 1:44
  • Yeah, it's frustrating because obviously the Raspberry Pi is fully capable of outputting audio, it just seems to be really difficult to do it from Python. I could probably knock up what I want with an ATTiny, I just really don't want to do the rest of my project in C. – MalphasWats Mar 20 '18 at 10:31
1

The code you have simply can't work.

  1. There is no way RPi.GPIO or Python can reliably software time PWM at a frequency of 16000Hz. That means the process must reliably re-schedule every 32µs.
  2. There is no way Python can reliably do a time.sleep(1/(c4*freq)). Where c4=261, freq=256, c4*freq=66816, so that is asking for a 15µs sleep.

Problem 1 is surmountable (but not with RPi.GPIO), you could use hardware timed PWM.

I don't see a solution for problem 2 unless you have a supercomputer to spare.

  • I'm pretty sure the second code block is wrong anyway - I don't completely understand how to do the equivalent in python. – MalphasWats Mar 18 '18 at 15:15

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.