import string
from typing import Any
from scipy.io import wavfile as wav
import numpy as np
import sounddevice as sd
import math
import sys

print("Tuner exercise")
SAMPLE_RATE = 44100
CHUNK = 2048
QUIET_THRESHOLD = 0.01
A4 = 440
DEBUG = True
NOTES = "A B♭/A♯ B C D♭/C♯ D E♭/D♯ E F F♯/G♭ G A♭/G♯".split()


# Get the note and octave using known note forumla
def calculate_note(frequency):
    if frequency > 0:
        note_num = round(math.log2(frequency / A4) * 12)
        octave = math.floor(4 + note_num / 12)
        rel_note = note_num % 12
        note = NOTES[rel_note]
        return f"{note}{octave}"
    return 0


# Attempt to get the dominant frequency by windowing our data sample, and taking an FFT
# In theory, the highest magnitude frequency of the FFT result should reflect what the human is trying to play in the signal
def dominant_frequency(data, samplerate):
    windowed_data = data * np.hanning(len(data))
    fft_result = np.fft.rfft(windowed_data)
    fft_magnitude = np.abs(fft_result)
    freqs = np.fft.rfftfreq(len(windowed_data), 1 / samplerate)
    peak_idx = np.argmax(fft_magnitude)
    dominant_freq = freqs[peak_idx]
    return dominant_freq


def calculate_rms(data):
    return np.sqrt(np.mean(data**2))


def audio_callback(indata: np.ndarray, frames: int, time: Any, status: Any):
    if status:
        print(f"Status: {status}")
    rms = calculate_rms(indata)

    # Ignore low noise to prevent spammy-ness and keep last audible result shown 
    if rms > QUIET_THRESHOLD:
        mono_data = np.mean(indata, axis=1)
        frequency = dominant_frequency(mono_data, SAMPLE_RATE)
        out =  f"\r{calculate_note(frequency)}"

        if DEBUG: out += f" Dominant Frequency: {frequency:.2f} Hz (RMS: {rms:.3f})"
        sys.stdout.write(out)
        sys.stdout.flush()
    else:
        pass


if DEBUG:
    print(sd.query_devices())


try:
    stream = sd.InputStream(
        device=15,
        callback=audio_callback,
        samplerate=SAMPLE_RATE,
        channels=1,
        blocksize=CHUNK,
    )
    stream.start()
    if DEBUG:
        print("channels? ", stream.channels)
        print("device? ", stream.device)
        print("active? ", stream.active)
    while True:
        pass
except KeyboardInterrupt:
    print("Audio recording stopped.")

finally:
    print("Stream closed.")

