Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Improve Audio #71

Open
wants to merge 3 commits into
base: master
from
Open
Changes from 1 commit
Commits
File filter...
Filter file types
Jump to…
Jump to file or symbol
Failed to load files and symbols.

Always

Just for now

@@ -14,15 +14,17 @@ class ModifiedExpressAudio:
def __init__(self):
self._max_sample_rate = 350_000
self._highest_supported_frequency = 20_000
self._normal_sample_length = 300
self._normal_sample_length = 100 # This can be just 2 for square wave
self._samples_change_frequency = self._max_sample_rate / self._normal_sample_length
self._sample_lengths = (
self._normal_sample_length,
self._max_sample_rate / self._highest_supported_frequency)
int(self._max_sample_rate / self._highest_supported_frequency))
self._speaker_enable = digitalio.DigitalInOut(board.SPEAKER_ENABLE)
self._speaker_enable.switch_to_output(value=False)
self._speaker_enable.value = True
self._raw_samples = tuple(self._generate_samples())
# List of samples lists for each of sine and square waveforms
self._raw_samples = self._generate_samples()
self._waveform_type_index = 0
self._audio_out = audioio.AudioOut(board.SPEAKER)

@staticmethod
@@ -34,19 +36,25 @@ def _sine_waveform(length, amplitude=1):
yield int(amplitude * max_amplitude * (math.sin(angle) + 1))

This comment has been minimized.

Copy link
@dhalbert

dhalbert Oct 4, 2019

Contributor

You might see if using a regular loop is noticeably faster than using yield. I'm not sure how much difference it would make due to the cost of sin(). Another idea is to have some precomputed sin() values. (Maybe you discussed that in the Monday meeting? I was not following closely.)

This comment has been minimized.

Copy link
@dcbriccetti

dcbriccetti Oct 4, 2019

Author Contributor

Thanks, Dan. I’m thinking Kattni (if it was she) used yield to avoid having two copies of the sample in memory at the same time, since it is loaded into an array. In any case, these sample-generating methods will no longer be the bottleneck.

angle += angle_change_per_sample

@staticmethod
def _square_waveform(length, amplitude=1):
max_amplitude = 2 ** 16 - 1
top = int(amplitude * max_amplitude)
return (top if i < length / 2 else 0 for i in range(length))

def _generate_samples(self):
return [audiocore.RawSample(array.array("H", ModifiedExpressAudio._sine_waveform(length)))
for length in self._sample_lengths]
'For each of sine and square waveforms, generate one sample each for low and high ranges'
return [[audiocore.RawSample(array.array("H", fn(length)))
for length in self._sample_lengths]
for fn in (ModifiedExpressAudio._sine_waveform, ModifiedExpressAudio._square_waveform)]

def start_tone(self, frequency):
if frequency <= self._highest_supported_frequency:
# Start playing a tone of the specified frequency (hz).
samples_index = 0 if frequency < self._samples_change_frequency else 1
length = self._sample_lengths[samples_index]
sample_to_play = self._raw_samples[samples_index]
sample_to_play = self._raw_samples[self._waveform_type_index][samples_index]
sample_to_play.sample_rate = int(length * frequency)
if not self._audio_out.playing:
self._audio_out.play(sample_to_play, loop=True)
self._audio_out.play(sample_to_play, loop=True)

def stop_tone(self):
if self._audio_out is not None and self._audio_out.playing:
@@ -59,24 +67,31 @@ def clear_audio(self):
self._audio_out = None


cpx = ModifiedExpressAudio()
def play_tone():
cpx.start_tone(220)
time.sleep(5)

STARTING_NOTE_FREQ = 32.71 # C1
STARTING_OCTAVE = 1
NOTE_SPACING_SECONDS = 0.2
KEYS = 'C Db D Eb E F Gb G Ab A Bb B'.split(' ') # Using b to represent ♭

def play_scale():
STARTING_NOTE_FREQ = 32.70319566257483 # C1
STARTING_OCTAVE = 1
NOTE_SPACING_SECONDS = 0.2
KEYS = 'C Db D Eb E F Gb G Ab A Bb B'.split(' ') # Using b to represent ♭

for reset in (False, ):
next_note_play_time = time.monotonic()
for octave_index in range(9):
for semitone_index in range(12):
semitones_above_start = octave_index * 12 + semitone_index
freq = STARTING_NOTE_FREQ * 2 ** (semitones_above_start / 12)
print(KEYS[semitone_index % 12] + str(octave_index + STARTING_OCTAVE), '\t', freq)
cpx.start_tone(freq)
next_note_play_time += NOTE_SPACING_SECONDS
time.sleep(next_note_play_time - time.monotonic())
cpx.stop_tone()
time.sleep(0.5)
for i in range(1, 2):
cpx._waveform_type_index = i
next_note_play_time = time.monotonic()
for octave_index in range(7):
for semitone_index in range(12):
semitones_above_start = octave_index * 12 + semitone_index
freq = STARTING_NOTE_FREQ * 2 ** (semitones_above_start / 12)
print(KEYS[semitone_index % 12] + str(octave_index + STARTING_OCTAVE), '\t', freq)
cpx.start_tone(freq)
next_note_play_time += NOTE_SPACING_SECONDS
time.sleep(next_note_play_time - time.monotonic())
# Not calling stop_tone()


cpx = ModifiedExpressAudio()
play_scale()
cpx.clear_audio()
ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.