import time
import pyaudio
from pydub import AudioSegment  
from math import ceil

#Mute alsa
import ctypes

ERROR_HANDLER_FUNC = ctypes.CFUNCTYPE(None, ctypes.c_char_p, ctypes.c_int,
                                      ctypes.c_char_p, ctypes.c_int,
                                      ctypes.c_char_p)

def py_error_handler(filename, line, function, err, fmt):
    pass

c_error_handler = ERROR_HANDLER_FUNC(py_error_handler)

try:
    asound = ctypes.cdll.LoadLibrary('libasound.so.2')
    asound.snd_lib_error_set_handler(c_error_handler)
except OSError:
    pass
#End mute alsa

class Audio():
    def __init__(self, rate=16000, chunk=8192, format=pyaudio.paInt16):
        self.rate = rate
        self.chunk = chunk
        self.format = format
        self.channels = 1
        self.frame = None
        self.frames = []
        self.stream = None
        self.duration = None
        self.recording = False
        self.target_volume = -20
        self.sound = None
        self.player = None

        self.play_sound = None
        self.play_rate = None
        self.play_chunk = 2048
        self.play_format = None
        self.play_channels = 1
        self.play_frames = []
        self.play_file = None

        self.p = pyaudio.PyAudio()
        
        def callback(in_data, frame_count, time_info, status):
            if self.recording:
                self.frames.append(in_data)
                if self.duration is not None and len(self.frames) >= int(self.rate / self.chunk * self.duration):
                    self.recording = False
            self.frame = in_data
            return (in_data, pyaudio.paContinue)

        self.stream = self.p.open(format=self.format,
                        channels=self.channels,
                        rate=self.rate,
                        input=True,
                        frames_per_buffer=self.chunk,
                        stream_callback = callback)

    def auto_volume(self, sound, target_dBFS):
        change_in_dBFS = target_dBFS - sound.dBFS
        return sound.apply_gain(change_in_dBFS)

    def start_record(self, file, target_volume=-20):
        self.recording = False
        self.file = file
        self.frames = []
        self.duration = None
        self.recording = True
        self.target_volume = target_volume
    
    def stop_record(self):
        self.recording = False
        self.sound = AudioSegment(b''.join(self.frames), sample_width=self.p.get_sample_size(pyaudio.paInt16), channels=self.channels, frame_rate=self.rate)
        if self.target_volume is not None:
            self.sound = self.auto_volume(self.sound, self.target_volume) 
        self.sound.export(self.file, format='wav')

    def record(self, file, duration, target_volume=-20):
        self.file = file
        self.recording = False
        self.frames = []
        self.duration = duration
        self.recording = True
        self.target_volume = target_volume
        while self.recording:
            time.sleep(0.001)
        self.stop_record()
        self.sound.export(self.file, format='wav')

    def sound_level(self):
        def mapping( x,  in_min,  in_max,  out_min,  out_max):
            result = (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min
            result = max(min(result, 100), 0)
            return result

        return round( mapping(self.sound_dBFS(), -50, -20, 0, 100), 2)

    def sound_dBFS(self):
        if self.frame is None:
            return -96.00
        else:
            return AudioSegment(self.frame, sample_width=self.p.get_sample_size(pyaudio.paInt16), channels=self.channels, frame_rate=self.rate).dBFS
        
        

    def play(self, file):
        self.start_play(file)
        while self.player.is_active():
            time.sleep(0.001)
        self.stop_play()

    def start_play(self, file):
        def callback(in_data, frame_count, time_info, status):
            if not self.play_frames:
                return (b'', pyaudio.paComplete)
            data = self.play_frames.pop(0)
            return (data, pyaudio.paContinue)

        self.play_sound = AudioSegment.from_file(file)

        if self.play_sound.channels == 1:
            self.play_sound = self.play_sound.set_channels(2)

        chunk_size = self.play_chunk * self.play_sound.sample_width * self.play_sound.channels

        number_of_chunks = ceil(len(self.play_sound.raw_data) / float(chunk_size))
        self.play_frames = [self.play_sound.raw_data[i * chunk_size:(i + 1) * chunk_size] for i in range(int(number_of_chunks))]

        self.player = self.p.open(format = self.p.get_format_from_width(self.play_sound.sample_width), 
            channels = self.play_sound.channels, 
            rate = self.play_sound.frame_rate,
            output = True,
            frames_per_buffer=self.play_chunk,
            stream_callback = callback)
    
        self.player.start_stream()
    
    def stop_play(self):
        self.player.stop_stream()
        self.play_frames = []
        self.player.close()

    def pause_play(self):
        self.player.stop_stream()

    def resume_play(self):
        self.player.start_stream()
    
    def play_time_remain(self):
        return round(len(self.play_frames)*self.play_chunk/self.play_sound.frame_rate, 2)