# cpb_bells.py

# 1. Demonstration of playing synthesized audio waveforms on the Adafruit Circuit Playground Bluefruit.
# 2. Demonstrate use of a Python class to encapsulate functionality.

# This demo is specific to the Adafruit Circuit Playground Bluefruit board but
# could be easily ported to microcontrollers supporting a speaker and audio PWM I/O.

#================================================================
# Import standard Python modules.
import time, array, math

# This demonstration is compatible with the high-level library, but does not need it, it instead
# directly uses the underlying hardware modules.
# from adafruit_circuitplayground import cp

# Import hardware I/O modules.
import board
import audiocore
import audiopwmio
import digitalio

#================================================================
try:
    # Attach to the speaker enable pin.  For compatibility with the
    # adafruit_circuitplayground library, if attaching fails, assume it is
    # already attached by the cp module.
    speaker_enable = digitalio.DigitalInOut(board.SPEAKER_ENABLE)
    speaker_enable.switch_to_output(value=False)
    print("Attached to SPEAKER_ENABLE.")
except ValueError:
    speaker_enable = cp._speaker_enable
    print("Using existing SPEAKER_ENABLE.")

#================================================================
# Synthesized audio sample generator and player.  The waveform generation takes
# a noticeable amount of time so the result is cached.  The pitch of the note is
# varied by modulating the sampling rate of the playback.

class Bell(object):
    def __init__(self, length=2000, cycles=55):
        super(Bell,self).__init__()

        # save the sample table properties
        self._length = length
        self._cycles = cycles

        # create a table of audio samples as signed 16-bit integers
        start = time.monotonic_ns()
        sample_array = array.array("h", self.wave_function(length, cycles))
        end = time.monotonic_ns()
        print(f"Bell sample generation took {1e-9*(end-start)} sec")
        
        # convert to a RawSample object for playback
        self.playable_sample = audiocore.RawSample(sample_array)

        # create an audio player attached to the speaker
        self.audio_output = audiopwmio.PWMAudioOut(board.SPEAKER)

        return

    # Define a generator function to produce the given number of waveform cycles
    # for a waveform table of a specified sample length.
    def wave_function(self, length, cycles):
        phase_rate = cycles * 2 * math.pi / length
        for i in range(length):
            phase = i * phase_rate
            amplitude = 10000 * (1.0 - (i / length))
            partial1 = math.sin(2 * phase)
            partial2 = math.sin(3 * phase)
            partial3 = math.sin(4 * phase)
            yield min(max(int(amplitude*(partial1 + partial2 + partial3)), -32768), 32767)

    def play(self, frequency):
        # Start playing the tone at the specified frequency (hz).
        sample_rate = int(self._length * frequency / self._cycles)
        print(f"Using sample rate {sample_rate}")
        self.playable_sample.sample_rate = sample_rate
        speaker_enable.value = True
        self.audio_output.play(self.playable_sample, loop=False)

    def stop(self):
        if self.audio_output.playing:
            self.audio_output.stop()
        speaker_enable.value = False

    def deinit(self):
        self.stop()
        self.audio_output.deinit()
        self.audio_output = None
        self.playable_sample = None

    # ----------------------------------------------------------------
    # Convert MIDI note value to frequency. This applies an equal temperament scale.
    def midi_to_freq(self, midi_note):
        MIDI_A0 = 21
        freq_A0 = 27.5
        return freq_A0 * math.pow(2.0, (midi_note - MIDI_A0) / 12.0)

    def note(self, note):
        self.play(self.midi_to_freq(note))

# ----------------------------------------------------------------
# Initialize global variables for the main loop.

# Create a Bell object, an instance of the Bell class.
synth = Bell()

# ----------------------------------------------------------------
# Enter the main event loop.
while True:
    # Play a major fifth interval starting on concert A: A4, E5, A4
    synth.play(440)
    time.sleep(1.0)
    synth.play(660)
    time.sleep(1.0)
    synth.play(440)
    time.sleep(1.0)

    # Play a chromatic scale up and down starting with C4 (middle-C, 261.63 Hz)
    for i in range(12):
        synth.note(60+i)
        time.sleep(0.2)
    for i in range(12):
        synth.note(72-i)
        time.sleep(0.1)
    synth.note(60)
    time.sleep(0.5)

    synth.stop()
    time.sleep(4)
    
