taped.util — taped 0.0.2 documentation

# Source code for taped.util

    from io import BytesIO

    import numpy as np
    import soundfile as sf
    from stream2py.sources.audio import PyAudioSourceReader

    # TODO: (wish) service this will builtins only

    # DFLT_INPUT_DEVICE_INDEX = find_a_default_input_device_index()
    DFLT_SR = 44100
    DFLT_N_CHANNELS = 1
    DFLT_SAMPLE_WIDTH = 2
    DFLT_CHK_SIZE = 1024 * 4
    DFLT_STREAM_BUF_SIZE_S = 60
    read_kwargs_for_sample_width = {
        2: dict(format='RAW', subtype='PCM_16', dtype='int16'),
        3: dict(format='RAW', subtype='PCM_24', dtype='int32'),
        4: dict(format='RAW', subtype='PCM_32', dtype='int32'),
    }

    def bytes_to_waveform(b: bytes, sr: int, n_channels: int, sample_width: int, dtype='int16') -> np.array:
        """Convert raw bytes to a numpy array cast to dtype

        :param b: bytes
        :param sr: sample rate
        :param n_channels: number of channels
        :param sample_width: sample byte width [2, 3, 4]
        :param dtype: data type used by numpy, i.e. dtype=np.int16 is the same as dtype='int16'
        :return: numpy.array
        """
        sample_width_to_subtype = {
            2: 'PCM_16',
            3: 'PCM_24',
            4: 'PCM_32',
        }
        return sf.read(BytesIO(b), samplerate=sr, channels=n_channels, format='RAW',
                       subtype=sample_width_to_subtype[sample_width], dtype=dtype)[0]

    def list_recording_device_index_names():
        """List (index, name) of available recording devices"""
        return sorted((d['index'], d['name']) for d in PyAudioSourceReader.list_device_info() if d['maxInputChannels'] > 0)

    # TODO: Merge with find_a_device_index
    def find_a_default_input_device_index(verbose=True):
        for index, name in list_recording_device_index_names():
            if 'microphone' in name.lower():
                if verbose:
                    print(f"Found {name}. Will use it as the default input device. It's index is {index}")
                return index
        for index, name in list_recording_device_index_names():
            if 'mic' in name.lower():
                if verbose:
                    print(f"Found {name}. Will use it as the default input device. It's index is {index}")
                return index

    def audio_pokes_version_of_bytes_to_waveform(b, sr=DFLT_SR, n_channels=DFLT_N_CHANNELS, sample_width=DFLT_SAMPLE_WIDTH):
        return sf.read(BytesIO(b), samplerate=sr, channels=n_channels, **read_kwargs_for_sample_width[sample_width])[0]

    def waveform_to_bytes(wf, sr=DFLT_SR, sample_width=DFLT_SAMPLE_WIDTH):
        b = BytesIO()
        subtype = read_kwargs_for_sample_width[sample_width]['subtype']
        sf.write(b, wf, samplerate=sr, format='RAW', subtype=subtype)
        b.seek(0)
        return b.read()

    # TODO: Test and merge with find_a_default_input_device_index
    def find_a_device_index(filt='microphone', dflt=None):
        if isinstance(filt, str):
            match_str = filt

            def filt(x):
                return match_str in x.get('name', match_str).lower()
        match = next(filter(filt, PyAudioSourceReader.list_device_info()), None)
        return (match is not None and match['index']) or dflt

    def ensure_source_input_device_index(input_device_index=None):
        if input_device_index is None:
            input_device_index = find_a_default_input_device_index()
        if input_device_index is not None:
            return input_device_index
        else:
            # TODO: Nicer way to print info (perhaps only relevant info, formated as table)
            print("Need a valid input_device_index. Calling live_audio_chks.list_device_info() to information about the "
                  "devices I can detect:\n")
            for item in PyAudioSourceReader.list_device_info():
                print(item)
                print("")
            print("---> Look in the list above and choose an input_device_index (it's called index in the printout above) "
                  "that seems to be right for you!")
            raise ValueError("Need a valid input_device_index")