def put_audio(self, ch1, ch2):
        """ Called by putOut()
            Send to the Output ch1, ch2 interleaved
            :param ch1:         Force data
            :param ch2:         Accel data
            :return:    None
        """
        self.recall_pickled_dict(
        )  # Load variables from the c.s.v. c.pickle_txt_fullpath
        SamplingFreq = v.SamplingFreq  # Sampling Frequency, Hz
        samples_acq = v.samples_acq  # n° of Samples acquired: c.n_blocks multiple
        Length = v.Length  # Pile length, m
        Speed = v.Speed  # Wave speed
        #
        FORMAT = pyaudio.paInt32  # Signed 32 bit (Little Endian byte order)
        CHANNELS = int(2)
        byte_width = 4
        bytes_size = CHANNELS * byte_width * c.N_FRAMES  # Bytes in each period
        """ Create the data_out alsaaudio object instance
            In PCM_NORMAL mode, the call will block if the kernel buffer is full, and until enough sound has been 
            played to allow the sound data to be buffered. The call always returns the size of the data provided.
            Syntax: class alsaaudio.PCM(type=PCM_PLAYBACK, mode=PCM_NORMAL, device='default', cardindex=-1)
            To find the sound device use: aplay -L:
            ATTENTION: the device full name must be without spaces !
                fuji:                                  "sysdefault"
                Pisound        direct hardware device: "hw:CARD=pisound,DEV=0"
                AudioBox 22VSL direct hardware device: "hw:CARD=VSL,DEV=0"
        """

        if c.IS_RASPBERRY: print("Machine is Raspberry Pi")
        else: print("Machine is not a Raspberry Pi")
        print("Audio device is n°", c.SOUND_DEVICE)
        #
        try:  # Creation af output audio object
            data_out = aa.PCM(aa.PCM_PLAYBACK,
                              aa.PCM_NORMAL,
                              device=c.SOUND_DEVICE)
            data_out.setchannels(CHANNELS)
            data_out.setrate(SampRate)
            data_out.setformat(FORMAT)
            data_out.setperiodsize(c.N_FRAMES)
            print("  - Success creating the output audio object.")
        except:
            print("  - Failed: an old audio process is running ?")
            print("    - Search the old process to be terminated.")
            #
            cmd = "ps aux | grep put_audio.py | head -n1"  # Search it and try to terminate
            res = subprocess.getoutput(cmd)
            pid = res[9:15].strip()
            sts = os.WEXITSTATUS(os.system('kill -TERM ' + pid))
            if sts is 0:  # Process closed successfully
                print(
                    "      - Found the old process: terminated with success.")
                try:  # Creation of output audio object
                    print(
                        "        - Second try to create an output audio object."
                    )
                    time.sleep(0.1)  # Indispensable greather than 0.01
                    data_out = aa.PCM(aa.PCM_PLAYBACK,
                                      aa.PCM_NORMAL,
                                      device=c.SOUND_DEVICE)
                    data_out.setchannels(CHANNELS)
                    data_out.setrate(SampRate)
                    data_out.setformat(FORMAT)
                    data_out.setperiodsize(c.N_FRAMES)
                    print(
                        "          - Success creating the output audio object."
                    )
                except:  # Failed,
                    doBeep(on_list=bip_5_S[0],
                           off_list=bip_5_S[1])  # On Bip of 5s
                    print(
                        "        - Failed second attempt to create the output audio object"
                    )
                #   #
            else:
                print("      - Failed attempt to find an old process.")
                return
            #   #
        #   # END if sts:
        # END try:...except:
        #
        #
        """ x1x2: vector of interleaved channels, Ch1, Ch2, Output signal
        """
        N = samples_acq  # Number of samples
        #
        ch1 /= (
            self.outGain * t.PI_INP_GAIN
        )  # Scaling Output from ~3 [t.PI_OUT_GAIN] to ~1, and for Input Gain
        ch2 /= (self.outGain * t.PI_INP_GAIN)
        ch1 *= c.full_scale_32  # Scaled to 32 bit
        ch2 *= c.full_scale_32
        x1 = ch1.astype(np.int32)  # Convert to int32, ch1
        x2 = ch2.astype(np.int32)  # Convert to int32, ch2
        #
        pk1_32 = np.max(np.abs(x1)) * (self.outGain)
        pk2_32 = np.max(np.abs(x2)) * (self.outGain)
        rms1_32 = np.std(x1) * (self.outGain)
        rms2_32 = np.std(x2) * (self.outGain)
        #
        print(
            "\n" +
            "Values are referred to acquired data and not equals to the values putted on Output"
        )
        print("rms1 = {:3.1f}".format((rms1_32 / c.full_scale_32) * 1000),
              "mV; ", "pk1  = {:3.1f}".format(
                  (pk1_32 / c.full_scale_32) * 1000), "mV")
        print("rms2 = {:3.1f}".format((rms2_32 / c.full_scale_32) * 1000),
              "mV; ", "pk2  = {:3.1f}".format(
                  (pk2_32 / c.full_scale_32) * 1000), "mV")
        #
        x1x2 = np.zeros(CHANNELS * samples_acq,
                        dtype=np.int32)  # Array for interleaved Ch1, Ch2 data
        x1x2[0::2] = x1  # Scaled Output gain, fill ch1: even indexes
        x1x2[
            1::
            2] = x2  # Scaled Output gain, fill ch1: odd  indexes                                                # Fill x1 at odd indexes:  ch2
        #
        out_big_buffer = bytearray(bytes_size * c.N_BLOCKS)
        out_big_buffer[:] = pack("l" * int(2 * c.N_OF_SAMPLES),
                                 *x1x2)  # Pack from numpy.int32 to bytes
        out_short_buffer = bytearray(
            bytes_size)  # Output array written one frame at time
        #
        os.system("sync")  # To empty write queue, free CPU
        beg = 0
        end = CHANNELS * c.N_FRAMES
        #
        for i in range(c.N_BLOCKS):  # Write data to stream pyaudio object
            out_short_buffer[:] = out_big_buffer[beg:end]  # As short blocks
            size = data_out.write(out_short_buffer)
            #print(size)
            beg = end
            end += bytes_size
        #   #
        data_out.close()
    def checkData(self, y1, y2):
        """ Called by ,fileLoad()
            :param y1: ch1 data array (Force)
            :param y2: ch2 data array (Accel)
            :return:    None
        """

        # /\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
        # PUT A PULSE ON Ch1 TO TEST GLITCH DETECTION
        #
        # noise_1 = np.random.rand(np.max(y1.shape)) / 500
        # noise_2 = np.random.rand(np.max(y1.shape)) / 500
        # y1      = noise_1
        # y2      = noise_2
        # y1[480] = 0.5
        #
        # /\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\

        # ========================== SATURATION CHECK BEGIN ==========================\
        #
        y1_Min = np.min(y1)  # Getting min / max
        y1_Max = np.max(y1)
        y2_Min = np.min(y2)
        y2_Max = np.max(y2)
        max_abs1 = max(abs(y1_Min), abs(y1_Max))  # ch1 absolute peak
        max_abs2 = max(abs(y2_Min), abs(y2_Max))  # ch2 absolute peak
        max_abs = max(max_abs1, max_abs2)  # Greater absolute maximum peak
        #
        if max_abs < c.satur_threshold:  # Saturation check
            is_Saturated = False
        else:
            is_Saturated = True
            doBeep(on_list=alarm_3_S[0], off_list=alarm_3_S[1])
            print(Saturation_msg, "max_abs =", max_abs)
        #   #
        # ========================== SATURATION CHECK END ============================/

        # ========================== GLITCH TESTING BEGIN ============================\
        #
        global plot_debug  # Access to the global plot_debug
        W = 1  # Lateral range width for each side around the peak sample
        is_err1 = False
        is_err2 = False
        #----------- NEW TEST BASED ON THE DIFFERENTIATION OF THE GLITCH (DIRAC DELTA FUNCTION)
        y1_d = np.diff(y1)
        y2_d = np.diff(y2)
        indx1 = min(min(
            np.nonzero(np.abs(y1) == max_abs1)))  # Index of max(abs(y1))
        indx2 = min(min(
            np.nonzero(np.abs(y2) == max_abs2)))  # Index of max(abs(y2))
        if indx1 < (np.max(y1.shape) - 2) and (np.max(
                abs(y1_d[indx1 - 1] - max_abs1)) < max_abs1 / 50):
            is_err1 = True
        #   #
        if indx2 < (np.max(y2.shape) - 2) and (np.max(
                abs(y2_d[indx1 - 1] - max_abs1)) < max_abs2 / 50):
            is_err2 = True
        #   #
        #-------------------------------------------------------------------------------
        if is_err1: print(Glitch_msg, "on Ch1")
        if is_err2: print(Glitch_msg, "on Ch2")
        if is_err1 or is_err2:
            doBeep(on_list=alarm_1_S[0], off_list=alarm_1_S[1])
        #   #
        # if is_err1 or is_err2 or plot_debug:
        #     mean1 = int(round(1000*mean_range1))
        #     max1  = int(round(1000*max_abs1 / 2))
        #     mean2 = int(round(1000*mean_range2))
        #     max2  = int(round(1000*max_abs2 / 2))
        #     print("==============================")
        #     print("  Glitches are absent if:")
        #     print("  mean_range > max_absX/2")
        #     print("==============================")
        #     print("  ch1:    {:03d}".format(mean1), "  {:03d}".format(max1), " [mV]")
        #     print("------------------------------")
        #     print("  ch2:    {:03d}".format(mean2), "  {:03d}".format(max2), "[mV]")
        #     print("------------------------------")
        #     print("  ch1 indexes:  ", range_indx1)
        #     print("  ch2 indexes:  ", range_indx2)
        #     print("------------------------------")
        # #   #
        # ============================ GLITCH TESTING END ============================/

        # ======================= CHANNELS SWAP TESTING BEGIN ========================\
        #
        """ Sometime the first acquisition can swap ch1 with ch2, here we detect that occurrence, and in the case, 
            we recovery by a swap between ch1 and ch2. 
            1) The autocorrelations of y1 and y2 are calculated.
            2) The max absolute peaks of the autocorrelations are calculated.
            3) Each one of the autocorrelation amplitudes are normalized using the absolute peaks of point (2).
            4) The normalized autocorrelations are rounded to 1 decimal to exclude little oscillations around 0.
            5) The differentiation of the sign changements of the normalized and rounded correlations is calculated.
            6) The differentiations of point (5) are cumulated to obtain the final scalar count.
            7) The ch1 Force signal is a well behaved and then bandlimited pulse with only two zero crossing 
              (excluding noise), by contrast the ch2 Accel signal being a derivative (accelerometer signal) 
              has surely more sign changements, consequently when the total sign changes of ch1 exceeds that of ch2 
              the channels are swapped. 
        """
        upper = int(3 * self.indx_echo_0)  # Upper index limit for checking
        corr_y1 = correlate(y1[0:upper], y1[0:upper], mode='same')
        corr_y2 = correlate(y2[0:upper], y2[0:upper], mode='same')
        pk_abs_corr_1 = np.max(np.abs(corr_y1))
        pk_abs_corr_2 = np.max(np.abs(corr_y2))
        corr_y1 = np.round(corr_y1 / pk_abs_corr_1, 1)
        corr_y2 = np.round(corr_y2 / pk_abs_corr_2, 1)
        nz_diff_corr_y1 = (np.diff(np.sign(corr_y1), n=1) != 0).sum()
        nz_diff_corr_y2 = (np.diff(np.sign(corr_y2), n=1) != 0).sum()
        #
        if nz_diff_corr_y1 < nz_diff_corr_y2:
            is_swapped = False
        else:
            is_swapped = True
        #   #
        if plot_debug:
            print("Actual loaded file n°", self.fileIndex)
            print("\n" + "Zero crossing comparison:", nz_diff_corr_y1, " <  ",
                  nz_diff_corr_y2, " ", not is_swapped)
        #   #
        print("            Good if:")
        print(" nz_diff_corr_y1 < nz_diff_corr_y2")
        print("-----------------------------------")
        print("       ", nz_diff_corr_y1, "       |       ", nz_diff_corr_y2)
        print("-----------------------------------")
        if is_swapped:
            print("\n" + "/" * 35)
            print("     ERROR: Swapped ch1, ch2")
            print("        CORRECTION DONE!")
            print("/" * 35 + "\n")
            y1, y2 = y2, y1  #  Swap Channels
            self.y1_full = self.y1
            self.y2_full = self.y2
        #   #
        # ======================== CHANNELS SWAP TESTING END =========================/
        self.plotData()
Exemple #3
0
def put_audio(plot_debug=False):
    """
        Getting Constants from Constants.py
        Variables must be extracted from the Variables_dict who is managed by Disk_IO.
    """
    """ Variables and constants
    """
    n_arg = len(sys.argv)  # Number of command-line arguments
    if n_arg > 3:
        print("Error: required 2 arguments:" + "\n" + \
              "f (free) or v (bounded)"   + "\n" + \
              "integer from 0 to 100"        + "\n" + \
              "Example: v 20")
        return
    #   #
    arg_list = sys.argv  # List of command-line arguments
    arg_1_str = str(arg_list[1])  # f (free) or v (bounded)
    arg_2_int = int(str(arg_list[2]))  # Integer value expressing echo %
    #
    if arg_1_str.upper() == "F":  # Positive value: Free Pile foot
        k_A_echo_0 = 1
        is_free = True
    elif arg_1_str.upper() == "V":  # Negative value: bounded Pile foot
        k_A_echo_0 = -1
        is_free = False
    else:
        print("Error in the 1.st input parameters: accepted values are :" + "\n" + \
              "f (free) or v (bounded)"                                + "\n" + \
              "you have entered: " + arg_1_str)
        return
    #   #
    if (0 <= arg_2_int) and (arg_2_int <= 100):
        k_A_echo_0 *= arg_2_int / 100
    else:
        print("Error in the 2.nd input parameters: accepted value is :" + "\n" + \
              "integer value expressing echo as a % (from 0 to 100)"    + "\n" + \
              "you have entered: " + str(arg_2_int))
        return
    #   #
    save_actual_pid()  # Save the PID to allow to kill the process
    recall_pickled_dict(
    )  # Load variables from the c.s.v. c.pickle_txt_fullpath
    #
    SampRate = v.SamplingFreq  # Sampling Frequency, Hz
    samples_acq = v.samples_acq  # N. of Samples acquired: c.n_blocks multiple
    Length = v.Length  # Pile length, m
    Speed = v.Speed  # Wave speed
    AveragesRequired = v.AveragesRequired + 4  # Averages to do plus a reserve for lost acquisition (glitches)
    ro_c = v.Density  # Actual Density [kg/m^3]
    Vc = Speed  # Actual Speed [m/s]
    D = v.Diam  # Actual Diameter m[]
    L = Length  # Actual Length m[]
    A = (np.pi * D**2) / 4  # Pile section Area.
    E = ro_c * Vc**2  # Young module in the RC: Pascal [N / m ^ 2]
    K = E * A / L  # Longitudinal Stiffness [N/m].
    M = A * ro_c * L  # Pile total Mass [kg].
    w0 = np.sqrt(K / M)  # Circular peak freq. of not dampen concentrated model
    #
    FORMAT = aa.PCM_FORMAT_S32_LE  # Int 4 Bytes signed
    CHANNELS = int(2)
    byte_width = 4
    bytes_size = CHANNELS * byte_width * c.N_FRAMES  # Bytes in each period
    """ Create the data_out alsaaudio object instance
        In PCM_NORMAL mode, the call will block if the kernel buffer is full, and until enough sound has been 
        played to allow the sound data to be buffered. The call always returns the size of the data provided.
        Syntax: class alsaaudio.PCM(type=PCM_PLAYBACK, mode=PCM_NORMAL, device='default', cardindex=-1)
        To find the sound device use: aplay -L:
        ATTENTION: the device full name must be without spaces !
            fuji:                                  "sysdefault"
            Pisound        direct hardware device: "hw:CARD=pisound,DEV=0"
            AudioBox 22VSL direct hardware device: "hw:CARD=VSL,DEV=0"
    """
    if c.IS_RASPBERRY: print("Machine is Raspberry Pi")
    else: print("Machine is not a Raspberry Pi")
    print("Audio device is:", c.SOUND_DEVICE)
    #
    try:  # Creation af output audio object
        data_out = aa.PCM(aa.PCM_PLAYBACK,
                          aa.PCM_NORMAL,
                          device=c.SOUND_DEVICE)
        data_out.setchannels(CHANNELS)
        data_out.setrate(SampRate)
        data_out.setformat(FORMAT)
        data_out.setperiodsize(c.N_FRAMES)
        print("  - Success creating the output audio object.")
    except:  # Failed: another audio process is running
        print("  - Failed to create an input audio object")
        print("    - Search an active process to be terminated.")
        #
        pid = None
        sts = 1
        cmd = "ps aux | grep get_audio.py | head -n1"  # Search get_audio.py and try to terminate
        res = subprocess.getoutput(cmd)
        pid = res[9:15].strip()
        if pid: sts = os.WEXITSTATUS(os.system('kill -TERM ' + pid))
        if sts is 0:  # Process closed successfully
            print("      - Found an active process: terminated with success.")
            try:  # Creation of input audio object
                print("        - Second try to create an input audio object.")
                time.sleep(0.5)  # Indispensable greather than 0.01
                data_out = aa.PCM(aa.PCM_PLAYBACK,
                                  aa.PCM_NORMAL,
                                  device=c.SOUND_DEVICE)
                data_out.setchannels(CHANNELS)
                data_out.setrate(SampRate)
                data_out.setformat(FORMAT)
                data_out.setperiodsize(c.N_FRAMES)
                print("          - Success creating the input audio object.")
            except:  # Failed,
                doBeep(on_list=bip_5_S[0], off_list=bip_5_S[1])  # On Bip of 5s
                print(
                    "        - Failed second attempt to create the input audio object: restart computer"
                )
                sys.exit()
            #   #
        else:
            print(
                "      - Old process not found: please restart the computer.")
            return
        #   #
    #   # END if sts:
    # END try:...except:
    #
    """ x1x2: vector of interleaved channels, Ch1, Ch2, output signal
    """
    #
    f_bound = v.Speed / (4 * v.Length)  # Vp / (4*L)
    f_free = v.Speed / (2 * v.Length)  # Vp / (2*L)
    if is_free:
        f_hp = f_free  # Pile foot is free
    else:
        f_hp = f_bound  # Pile foot is bounded
    #   #
    A_pk_32 = c.full_scale_32  # Peak Amplitude referr. to 32 bit int f.s.
    dt = 1.0 / SampRate  # Sampling period
    T = 1  # Total signal time length
    N = samples_acq  # Number of samples
    fnyquist = 0.5 * SampRate
    T_effective = HAMMER_DT  # Pulse equivalent length
    T_pulse = T_effective * 3 / 2  # Length at the half sine base
    N_pulse = round(T_pulse / dt)  # Number of pulse samples
    t = np.arange(N_pulse) * dt  # Time length of the pulse sample
    Head_zeros_N = int(0.5 * T_effective / dt)  # Number of zeros prepended
    Head_zeros = np.zeros(Head_zeros_N)  # Header of zeros
    pulse = np.sin(np.pi * t /
                   T_pulse) * c.PUT_AUDIO_PEAK  # Pulse as a positive half sine
    pulse_echo_0 = k_A_echo_0 * pulse  # Pulse echo_0: >0 free, <0 bounded
    T_echo_0 = 2 * Length / Speed  # Return time of echo_0
    indx_echo_0 = round(
        T_echo_0 / dt) + Head_zeros_N  # Index of the 1.st echo_0 sample
    #
    for n in range(int(AveragesRequired)):
        time.sleep(4)
        x1f = np.zeros(N)  # Array of 0's, ch1
        x2f = np.zeros(N)  # Array of 0's, ch2
        x1f[0:Head_zeros_N] = Head_zeros  # Put Head_zeros at index 0 for ch1
        x2f[0:Head_zeros_N] = Head_zeros  # Put Head_zeros at index 0 for ch2
        x1f[Head_zeros_N:Head_zeros_N +
            N_pulse] = pulse  # Put pulse after Head_zeros for ch1
        x2f[Head_zeros_N:Head_zeros_N +
            N_pulse] = pulse  # Put pulse after Head_zeros for ch2
        x2f[indx_echo_0:indx_echo_0 +
            N_pulse] = pulse_echo_0  # Put echo_0 delayed pulse on ch2
        #
        # Insertion of a HP 1.stth order filter on the Accel channel
        pad = 1000  # WAS 300
        w_d = f_hp / fnyquist  # Digital pulsation, 0..1, where 1 => fnyquist
        b, a = butter(N=1, Wn=w_d,
                      btype='high')  # HP Butterworth filter 1.st order
        x2f = lfilter(b, a, x2f)  # Filtering the Force signal
        #
        # Insertion on the Accel channel of the differentiation
        #
        x2f = np.diff(x2f, n=1,
                      append=0) / dt  # Differentiate x2f with a 0 appended
        x2f_pk = np.max(np.abs(x2f))  # Absolute peak value of x2f
        x2f /= x2f_pk  # Normalization unity peak, then
        #
        # Insertion of a LP 4.th order filter
        f_lp = 3 / T_pulse  # Hammer head response simulation
        w_d = f_lp / fnyquist  # Digital pulsation, 0..1, where 1 => fnyquist
        b, a = butter(
            N=2, Wn=w_d, btype='low'
        )  # WAS butter(N=2 bessel Digital LP Bessel filter 2.nd order
        x1f = filtfilt(b, a, x1f, padlen=pad)  # Filtering the Force signal
        x2f = filtfilt(b, a, x2f, padlen=pad)  # Filtering the Accel signal
        #                                                           #   scaling for Pisound Output Gain and then
        x2f *= c.PUT_AUDIO_PEAK  #   scaling for require Peak value
        #
        x1f *= HAMMER_SIGN  # Apply the polarity of the Force cell
        x2f *= ACCEL_SIGN  # Apply the polarity of Accelerometer
        #
        x1f *= A_pk_32  # Scale Hammer        to 32 bit signed full scale
        x2f *= A_pk_32  # Scale Accelerometer to 32 bit signed full scale
        #
        x1 = x1f.astype(np.int32)  # Convert to int32, ch1
        x2 = x2f.astype(np.int32)  # Convert to int32, ch2
        #
        pk1_32 = np.max(np.abs(x1))
        pk2_32 = np.max(np.abs(x2))
        rms1_32 = np.std(x1)
        rms2_32 = np.std(x2)
        #
        print("\n" + "Started pulse N°", n + 1)
        print(
            "rms1              = {:3.1f}".format(
                (rms1_32 / c.full_scale_32) * 1000), "mV; ",
            "pk1               = {:3.1f}".format(
                (pk1_32 / c.full_scale_32) * 1000), "mV")
        print(
            "rms2              = {:3.1f}".format(
                (rms2_32 / c.full_scale_32) * 1000), "mV; ",
            "pk2               = {:3.1f}".format(
                (pk2_32 / c.full_scale_32) * 1000), "mV")
        #
        x1x2 = np.zeros(CHANNELS * samples_acq,
                        dtype=np.int32)  # Array for interleaved Ch1, Ch2 data
        x1x2[
            0::
            2] = x1 / PI_OUT_GAIN  # Scaled output gain, fill ch1: even indexes
        x1x2[
            1::
            2] = x2 / PI_OUT_GAIN  # Scaled output gain, fill ch1: odd  indexes                                                # Fill x1 at odd indexes:  ch2
        #
        out_big_buffer = bytearray(bytes_size * c.N_BLOCKS)
        out_big_buffer[:] = pack("l" * int(2 * c.N_OF_SAMPLES),
                                 *x1x2)  # Pack from numpy.int32 to bytes
        out_short_buffer = bytearray(
            bytes_size)  # Output array written one frame at time
        #
        beg = 0
        end = bytes_size
        for i in range(c.N_BLOCKS):  # Write data to data_out alsaaudio object
            out_short_buffer[:] = out_big_buffer[beg:end]  # As short blocks
            size = data_out.write(out_short_buffer)
            #print(size)
            beg = end
            end += bytes_size
        #   #
        #time.sleep(3.0)
        #
        if plot_debug:
            sel = int(15 * indx_echo_0)  # Was 1.5
            mx1 = max(np.abs(x1f[0:sel] / A_pk_32))
            mx2 = max(np.abs(x2f[0:sel] / A_pk_32))
            mx = 1.1 * max(mx1, mx2)
            tp = np.arange(0, N, 1)  # * dt
            fig = plt.figure(1)
            fig.set_size_inches(w=3, h=4)
            fig.subplots_adjust(hspace=0.50)
            plt.subplot(211)
            plt.plot(tp[0:sel], x1f[0:sel] / A_pk_32)
            plt.title("put_audio: Hammer")
            # plt.xlabel("Time [s]")
            plt.xlabel('Samples [i]')
            plt.ylabel("Force")
            plt.grid(True)
            #[xl, xh, yl, yh] = plt.axis()
            plt.axis([tp[0], tp[sel], -mx, mx])
            #
            plt.subplot(212)
            plt.plot(tp[0:sel], x2f[0:sel] / A_pk_32)
            plt.title("put_audio: Accel")
            # plt.xlabel("Time [s]")
            plt.xlabel('Samples [i]')
            plt.ylabel("Accel")
            plt.grid(True)
            #[xl, xh, yl, yh] = plt.axis()
            plt.axis([tp[0], tp[sel], -mx, mx])
            #
            plt.show()
            #time.sleep(0.1)
        # END if plot_debug:
    # END for n in range(AveragesRequired):
    #
    data_out.close()
    print("put_audio terminated")
Exemple #4
0
def get_audio(plot_debug=False):
    """ Called by
        :param:     plot_debug, if True do a debug plots
        :return:    None
        Acquire Data under a trigger mode, then save the Data to the HD.
        Get Constants from Constants.py
        Variables will be extracted from the Variables_dict who is managed by Disk_IO.
        When debug==True plot acquired data as diagnostic.
    """
    recall_pickled_dict(
    )  # Load variables from the c.s.v. c.pickle_txt_fullpath
    SampRate = v.SamplingFreq  # Sampling Frequency, Hz
    TrigLevel = v.TrigLevel  # Actual Trigger level as a %
    AveragesRequired = v.AveragesRequired  # Required Averages number
    Length = v.Length  # Pile length
    Speed = v.Speed  # P wave speed in concrete
    SamplingFreq = v.SamplingFreq  # Sampling frequency
    T_echo_0 = 2 * Length / Speed  # Return time of echo_0
    indx_echo_0 = round(T_echo_0 *
                        SamplingFreq)  # Index of the 1.st echo_0 sample
    #
    save_actual_pid()  # Save the PID to a file, for successive kill
    #
    """ Called at each new Acquisition, remove files at c.temp_data_path, if the dir was removed, recreate it.
    """
    create_and_clear(c.temp_data_path, delete_files=True)
    #
    TrigLevel_32 = (
        TrigLevel / 100
    ) * c.full_scale_32  # Trigger level espressed as true amplitude referred to 1
    pk_foot = 1 / 100  # Relative factor for the pulse pedestal
    FORMAT = aa.PCM_FORMAT_S32_LE  # Signed 32 bit (Little Endian byte order)
    CHANNELS = int(2)
    byte_width = 4  # Bytes in one signed 32 bit sample
    bytes_size = CHANNELS * byte_width * c.N_FRAMES  # bytes in each period (frame) e.g. 2 * 4 * 256
    #
    """ Create the inp alsaaudio object instance
        PCM_NORMAL: Blocking Mode, not a true blocking as said in the docs, may return a variable number of frames,
        then is necessary to wait in a loop until the c.n_frames are returned and the buffer is full.
        To find the sound device use: aplay -L:
        ATTENTION: the device full name must be without spaces !
            fuji:                                  "sysdefault"
            Pisound        direct hardware device: "hw:CARD=pisound,DEV=0"
            AudioBox 22VSL direct hardware device: "hw:CARD=VSL,DEV=0"
    """
    print()
    print("=" * 50)
    print("   Started '" + __file__ + "' as a separate executable")
    print("=" * 50)
    if c.IS_RASPBERRY: print("Machine is Raspberry Pi")
    else: print("Machine is not a Raspberry Pi")
    print("Audio device is:", c.SOUND_DEVICE)
    #
    try:  # Creation af input audio object
        print("- Try to create an input audio object.")
        inp = aa.PCM(aa.PCM_CAPTURE, aa.PCM_NORMAL, device=c.SOUND_DEVICE)
        inp.setchannels(CHANNELS)
        inp.setrate(SampRate)
        inp.setformat(FORMAT)
        inp.setperiodsize(c.N_FRAMES)
        print("  - Success creating the input audio object.")
    except:  # Failed: another audio process is running
        print("  - Failed to create an input audio object")
        print("    - Search an active process to be terminated.")
        #
        pid = None
        sts = 1
        cmd = "ps aux | grep get_audio.py | head -n1"  # Search it and try to terminate
        res = subprocess.getoutput(cmd)
        pid = res[9:15].strip()
        if pid: sts = os.WEXITSTATUS(os.system('kill -TERM ' + pid))
        if sts is 0:  # Process closed successfully
            print("      - Found an active process: terminated with success.")
            try:  # Creation of input audio object
                print("        - Second try to create an input audio object.")
                time.sleep(0.1)  # Indispensable greather than 0.01
                inp = aa.PCM(aa.PCM_CAPTURE,
                             aa.PCM_NORMAL,
                             device=c.SOUND_DEVICE)
                inp.setchannels(CHANNELS)
                inp.setrate(SampRate)
                inp.setformat(FORMAT)
                inp.setperiodsize(c.N_FRAMES)
                print("          - Success creating the input audio object.")
            except:  # Failed,
                doBeep(on_list=bip_5_S[0], off_list=bip_5_S[1])  # On Bip of 5s
                print(
                    "        - Failed second attempt to create the input audio object"
                )
            #   #
        else:
            print(
                "      - Old process not found: please restart the computer.")
            return
        #   #
    #   # END if sts:
    # END try:...except:
    #
    """ Preacquisition needed to flush bad data in buffers and to inhibit the swap of channels at first run.
    """
    while 1:  # Pay attention inp.read() return a tuple (i.e. immutable)
        l, inp_short_buffer = inp.read(
        )  # l must be equal to c.n_frames; blocked until filled
        if l == c.N_FRAMES:
            print("Preacq: l=", l)
            break
    #   #   #
    #
    del inp_short_buffer  # Being a tuple i.e. immutable must be destroyed to reuse the name
    i = 1
    while i <= int(AveragesRequired):
        """ 
            Wait until the Trigger level is reached getting frames continuously,
            the format is long i.e. int32; the AD has 24 bit, 8 MSBits are zeros.
            The Trigger Channel is Ch1: Even samples: 0, 2, 4,... => Ch1
            The variable inp_short_buffer cannot be predefined as a bytearray (that is mutable) 
            because inp.read() return an array of bytes (that is immutable).
        """
        inp_old_buffer = bytearray(bytes_size)
        inp_big_buffer = bytearray(bytes_size * c.N_BLOCKS)
        beg = 0
        end = bytes_size
        os.system("sync")  # To empty write queue
        #
        # ====================== Trigger loop Begin ======================\
        #
        print("\n" + "=o" * 25 + "=")
        if do_plot: time.sleep(2)  # For testing, wait to close the plot window
        else: time.sleep(1)
        print("Waiting for Trigger n.", i, "...")
        while True:
            while 1:  # Pay attention inp.read() return a tuple (i.e. immutable)
                l, inp_short_buffer = inp.read(
                )  # l must be equal to c.n_frames; blocked until filled
                if l == c.N_FRAMES:
                    break  # Check of l can reveal a casual error
                else:
                    continue
            #   #
            """ From the array of bytes inp_short_buffer containing int32 ch1, ch2 as blocks of 4 bytes interleaved, 
                get the tuple ch1_32  discarding each successive 4 bytes represented by xxxx
                (corresponding to ch2, i.e. get only ch1 values), for a total of c.n_frames.
            """
            if len(inp_short_buffer) != bytes_size:
                continue  # 2.nd check because the first may fail !!!
            ch1_int32 = unpack(
                'ixxxx' * c.N_FRAMES, inp_short_buffer
            )  # Get tuple (int ch1 only) from tuple of bytes
            ch1_arr_32 = np.array(ch1_int32)  # From tuple to np array
            pk1_32 = np.max(
                np.abs(ch1_arr_32))  # Get absolute peak value of ch1 as int32
            rms1_32 = np.std(
                ch1_arr_32
            )  # Check for single or few noise spike on ch1 as int32
            if pk1_32 < TrigLevel_32:  # No Trigger
                inp_old_buffer = inp_short_buffer  # No Trigger, save as a possible pre-trigger
                continue
            else:  # Get Trigger
                """ The following print is only for testing: must been leaved commented.
                    Represents values of the trigger frame.
                """
                #print("Trig. rms1 = {:3.1f}".format((rms1_32 / c.full_scale_32)*1000), "mV")
                #print("Trig. pk1  = {:3.1f}".format((pk1_32  / c.full_scale_32)*1000), "mV")
                inp_big_buffer[
                    beg:end] = inp_old_buffer  # Pre-Trigger block of data
                beg = end  # Prepare 2.nd block address
                end += bytes_size
                inp_big_buffer[
                    beg:
                    end] = inp_short_buffer  # Insert 2.nd block of Trigger data
                break
            #   #
        #   #
        # END while True:
        # ======================= Trigger loop End =======================/
        #
        # ====================== Acquisition loop start ==================\
        j = 2  # Block index: 0 and 1 blocks inserted inside the Trigger loop
        while j < c.N_BLOCKS:
            beg = end
            end += bytes_size
            while 1:  # Pay attention inp.read() return a tuple (i.e. immutable)
                l, inp_short_buffer = inp.read(
                )  # l: n. of frames for each channel: BLOCKING !!!
                if l == c.N_FRAMES:
                    break
            #   #   #
            #
            inp_big_buffer[beg:end] = inp_short_buffer  # Packing blocks
            j += 1
        #   #
        # ====================== Acquisition loop end ====================/
        #
        """ Unpacking bytes to integers (32 bit) in the tuple tpl_i32. 
            Then separating interleaved Channels, Ch1, Ch2, getting c.N_of_samples points for channel.
            Finally we have y1, y2 as np.arrays of float64, and scaled to +/-1 and compensated for Pisound Input Gain.
            To verify the correctness of the formatting string: calcsize('ii') returns 8
        """
        tpl_i32 = unpack(
            'ii' * c.N_OF_SAMPLES,
            inp_big_buffer)  # Packed bytes and interleaved: Ch1, Ch2
        y1_tpl_i32 = tpl_i32[0:c.N_OF_SAMPLES *
                             CHANNELS:2]  # Even: Ch1 as integer 32
        y2_tpl_i32 = tpl_i32[1:c.N_OF_SAMPLES * CHANNELS +
                             1:2]  # Odd:  Ch2 as integer 32
        y1 = np.array(
            y1_tpl_i32
        ) / c.full_scale_32  # Scaled to +/- 1 full scale                                      # Go to float 8 bytes
        y2 = np.array(y2_tpl_i32) / c.full_scale_32
        y1 /= PI_INP_GAIN  # Pisound board Input Gain compensation
        y2 /= PI_INP_GAIN
        #

        # /\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
        # PUT A PULSE ON Ch1 TO TEST GLITCH DETECTION
        #
        # noise_1 = np.random.rand(np.max(y1.shape)) / 500
        # noise_2 = np.random.rand(np.max(y1.shape)) / 500
        # y1      = noise_1
        # y2      = noise_2
        # y1[480] = 0.5
        #
        # /\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\

        # ========================== SATURATION CHECK BEGIN ==========================\
        #
        y1_Min = np.min(y1)  # Getting min / max
        y1_Max = np.max(y1)
        y2_Min = np.min(y2)
        y2_Max = np.max(y2)
        max_abs1 = max(abs(y1_Min), abs(y1_Max))  # ch1 absolute peak
        max_abs2 = max(abs(y2_Min), abs(y2_Max))  # ch2 absolute peak
        max_abs = max(max_abs1, max_abs2)  # Greater absolute maximum peak
        #
        if max_abs < c.satur_threshold:  # Saturation check
            is_Saturated = False
        else:
            is_Saturated = True
            doBeep(on_list=alarm_3_S[0], off_list=alarm_3_S[1])
            print(Saturation_msg, "max_abs =", max_abs)
        #   #
        # ========================== SATURATION CHECK END ============================/

        # ========================== GLITCH TESTING BEGIN ============================\
        #
        W = 1  # Range minimum width
        is_err1 = False
        is_err2 = False
        #----------- NEW TEST BASED ON THE DIFFERENTIATION OF THE GLITCH (DIRAC DELTA FUNCTION)
        y1_d = np.diff(y1)
        y2_d = np.diff(y2)
        indx1 = min(min(
            np.nonzero(np.abs(y1) == max_abs1)))  # Index of max(abs(y1))
        indx2 = min(min(
            np.nonzero(np.abs(y2) == max_abs2)))  # Index of max(abs(y2))
        if indx1 < (np.max(y1.shape) - 2) and (np.max(
                abs(y1_d[indx1 - 1] - max_abs1)) < max_abs1 / 50):
            is_err1 = True
        #   #
        if indx2 < (np.max(y2.shape) - 2) and (np.max(
                abs(y2_d[indx1 - 1] - max_abs1)) < max_abs2 / 50):
            is_err2 = True
        #   #
        #-------------------------------------------------------------------------------
        print("")
        if is_err1: print(Glitch_msg, "Ch1")
        if is_err2: print(Glitch_msg, "Ch2")
        if is_err1 or is_err2:  # Glitches counted as saturation:
            doBeep(on_list=alarm_1_S[0], off_list=alarm_1_S[1])
            is_Saturated = True  #   the acquisition will be repeated
        #   #
        # if is_err1 or is_err2 or plot_debug:
        #     mean1 = int(round(1000*mean_range1))
        #     max1  = int(round(1000*max_abs1 / 2))
        #     mean2 = int(round(1000*mean_range2))
        #     max2  = int(round(1000*max_abs2 / 2))
        #     print("==============================")
        #     print("  Glitches are absent if:")
        #     print("  mean_range > max_absX/2")
        #     print("==============================")
        #     print("  ch1:    {:03d}".format(mean1), "  {:03d}".format(max1), " [mV]")
        #     print("------------------------------")
        #     print("  ch2:    {:03d}".format(mean2), "  {:03d}".format(max2), "[mV]")
        #     print("------------------------------")
        #     print("  ch1 indexes:  ", range_indx1)
        #     print("  ch2 indexes:  ", range_indx2)
        #     print("------------------------------")
        #   #
        # ============================ GLITCH TESTING END ============================/

        # ======================= CHANNELS SWAP TESTING BEGIN ========================\
        #
        """ Sometime the first acquisition can swap ch1 with ch2, here we detect that occurrence, and in the case, 
            we recovery by a swap between ch1 and ch2. 
            1) The autocorrelations of y1 and y2 are calculated.
            2) The max absolute peaks of the autocorrelations are calculated.
            3) Each one of the autocorrelation amplitudes are normalized using the absolute peaks of point (2).
            4) The normalized autocorrelations are rounded to 1 decimal to exclude little oscillations around 0.
            5) The differentiation of the sign changements of the normalized and rounded correlations is calculated.
            6) The differentiations of point (5) are cumulated to obtain the final scalar count.
            7) The ch1 Force signal is a well behaved and then bandlimited pulse with only two zero crossing 
              (excluding noise), by contrast the ch2 Accel signal being a derivative (accelerometer signal) 
              has surely more sign changements, consequently when the total sign changes of ch1 exceeds that of ch2 
              the channels are swapped. 
        """
        upper = int(3 * indx_echo_0)  # Upper index limit for checking
        corr_y1 = correlate(y1[0:upper], y1[0:upper], mode='same')
        corr_y2 = correlate(y2[0:upper], y2[0:upper], mode='same')
        pk_abs_corr_1 = np.max(np.abs(corr_y1))
        pk_abs_corr_2 = np.max(np.abs(corr_y2))
        corr_y1 = np.round(corr_y1 / pk_abs_corr_1, 1)
        corr_y2 = np.round(corr_y2 / pk_abs_corr_2, 1)
        nz_diff_corr_y1 = (np.diff(np.sign(corr_y1), n=1) != 0).sum()
        nz_diff_corr_y2 = (np.diff(np.sign(corr_y2), n=1) != 0).sum()
        #
        if nz_diff_corr_y1 < nz_diff_corr_y2:
            is_swapped = False
        else:
            is_swapped = True
        #   #
        if plot_debug:
            print("\n" + "Zero crossing comparison:", nz_diff_corr_y1, " <  ",
                  nz_diff_corr_y2, " ", not is_swapped)
        #   #
        print("            Good if:")
        print(" nz_diff_corr_y1 < nz_diff_corr_y2")
        print("-----------------------------------")
        print("       ", nz_diff_corr_y1, "       |       ", nz_diff_corr_y2)
        print("-----------------------------------")
        if is_swapped:
            print("\n" + "/" * 35)
            print("     ERROR: Swapped ch1, ch2")
            print("        CORRECTION DONE!")
            print("/" * 35 + "\n")
            y1, y2 = y2, y1  # Restore from swap error
        #   #
        # ======================== CHANNELS SWAP TESTING END =========================/

        if not is_Saturated:
            """ If is_Saturated returns to the external acquisition loop: while i <= int(AveragesRequired)
                Preparing good data for storage:
                Relocation of the start of the inputrms1_32 signal in y1, y2, to eliminate the pre-trigger 
                part that has a variable length. That part will be shortened as follows: 
                a)  Search the first index where the Pulse input Data array (y1) is bigger than pk * pk_foot. 
                    The index of that basement point will be named index_Trig.
                b)  The new starting index is obtained decreasing, as possible, index_Trig by a fixed amount, 
                    in order to assure the inclusion of sufficient initial part of the signal. 
                    The corresponding amount of time, guard_T, will be equal to the hammer 
                    pulse width t.HAMMER_DT, the number of samples will be N_guard. 
            """
            y1_shape = y1.shape  # Array y1 dimensions
            y2_shape = y2.shape  # Array y2 dimensions
            if not (np.max(y1_shape) == np.max(y2_shape)):  # Length comparison
                print(Length_msg)
                print("get_audio: y1_shape =", y1_shape)
                print("get_audio: y2_shape =", y2_shape)
                doBeep(on_list=bip_1_5_S[0], off_list=bip_1_5_S[1])
                return
            #   #
            # ===============================  Relocation Begin =========================\
            #
            pk1 = pk1_32 / c.full_scale_32  # Peak scaled to +/- 1
            y1_length = np.max(y1_shape)  # Length of y1 array
            indexes = np.array(np.nonzero(
                abs(y1) > pk1 *
                pk_foot))[0, :]  # Array of indexes, [0,:] => from 1xN to N
            len_indexes = len(indexes)
            print("len_indexes =", len_indexes)
            if len_indexes != 0:
                index_Trig = np.amin(
                    indexes)  # Lowest index i.e. index pedestal start
            else:
                index_Trig = 0
            #   #
            dT = 1.0 / SampRate  # Sampling time
            guard_T = HAMMER_DT  # Safety margin time, before Pulse pedestal
            N_guard = int(round(guard_T /
                                dT))  # Corresponding guard margin as samples
            if index_Trig > N_guard:  # Guard must begin after y1[0]
                new_start = index_Trig - N_guard  # Index (>=0) for the new starting point
                tmp_array = np.zeros(
                    y1_length)  # Temporary empty array for new y1
                tmp_array[0:-1 - new_start] = copy.copy(
                    y1[new_start:-1]
                )  # Fill y1 without the guard section of y1
                # leaves a tail of zeros
                y1 = copy.copy(tmp_array)  # y1 replaced by relocated data
                tmp_array = np.zeros(np.max(
                    y2.shape))  # Temporary empty array for new y2
                tmp_array[0:-1 - new_start] = copy.copy(
                    y2[new_start:-1]
                )  # Fill y2 without the guard section of y2
                y2 = copy.copy(tmp_array)  # y2 replaced
                tmp_length = np.max(y1.shape)  # Actual length of y1 array
                if tmp_length != y1_length:
                    print(
                        "\n" +
                        "ERROR in get_audio: Length of y1, y2 modified erroneously"
                    )
                #   #
            else:  # Leave the signal unaltered
                pass
            #   #
            #===============================  Relocation End ===========================/
            #
            pk1 = np.max(np.abs(y1))
            pk2 = np.max(np.abs(y2))
            rms1 = np.std(y1)
            rms2 = np.std(y2)
            print("\n" + "Saving data to Disc")
            print("rms1 = {:3.1f}".format(rms1 * 1000),
                  "mV;  pk1  = {:3.1f}".format(pk1 * 1000), "mV")
            print("rms2 = {:3.1f}".format(rms2 * 1000),
                  "mV;  pk2  = {:3.1f}".format(pk2 * 1000), "mV")
            """ Save data to disc
            """
            name_1 = "/y1_" + str('{:02d}'.format(i))
            name_2 = "/y2_" + str('{:02d}'.format(i))
            path_1 = c.temp_data_path + name_1
            path_2 = c.temp_data_path + name_2
            #
            if c.sav_mode == "npy":
                """ Saving in Numpy binary file, not compressed, add extension .npy 
                """
                np.save(path_1, y1)  # Uncompressed format
                np.save(path_2, y2)
            elif c.sav_mode == "npz":
                """ Saving in Numpy binary file, compressed, add extension .npz 
                """
                np.savez_compressed(path_1, y1=y1)  # Compressed format
                np.savez_compressed(path_2, y2=y2)
            #   #
            elif c.sav_mode == "mat":
                """ Saving in Matlab format V.5, compressed, add automatically the extension .mat
                    loadmat() will return the vector in a dictionary ex:
                    d    = scipy.io.loadmat("vector.mat)
                    vect = d["vect"]
                    vect = d1["y1"]
                    and with one more dimension added ("column"):
                    original vect.shape -> (n,), returned vect.shape -> (n, 1); 
                    to return to the original dimension:
                    vect_reshaped = vect[:,0]
                """
                scipy.io.savemat(path_1, {"y1": y1},
                                 do_compression=True,
                                 oned_as="column")
                scipy.io.savemat(path_2, {"y2": y2},
                                 do_compression=True,
                                 oned_as="column")
            #   #
            print("New acquired data:", name_1, name_2)
            print("=o" * 25 + "=")
            save_blows(c.blows_list_txt, name_1, name_2)
            os.system("sync")  # Delayed write causes a random delay
            time.sleep(0.2)  # Delays for x seconds
            if plot_debug:
                sel = int(15 * indx_echo_0)  # was 1.5
                mx1 = max(np.abs(y1[0:sel]))
                mx2 = max(np.abs(y2[0:sel]))
                mx = 1.1 * max(mx1, mx2)
                tp = np.arange(c.N_OF_SAMPLES
                               )  # * 1.0 / SampRate            # Time vector
                fig = plt.figure(1)
                fig.set_size_inches(w=3, h=4)
                fig.subplots_adjust(hspace=0.50)
                plt.subplot(211)
                plt.plot(tp[0:sel], y1[0:sel])
                plt.title('get_audio Hammer')
                # plt.xlabel('Time [s]')
                plt.xlabel('Samples [i]')
                plt.ylabel('Force')
                plt.grid(True)
                plt.axis([tp[0], tp[sel], -mx, mx])
                #
                plt.subplot(212)
                plt.plot(tp[0:sel], y2[0:sel])
                plt.title('get_audio Accel')
                # plt.xlabel('Time [s]')
                plt.xlabel('Samples [i]')
                plt.ylabel('Accel')
                plt.grid(True)
                plt.axis([tp[0], tp[sel], -mx, mx])
                #
                plt.show()
                time.sleep(0.1)
            # END if plot_debug:
            i += 1
        # END if not is_Saturated):
    # END while i <= int(AveragesRequired):
    #
    inp.close()  # Close inp alsaaudio object
    #
    print("get_audio: closed input device, terminated")
Exemple #5
0
def get_audio(plot_debug=False):
    """ Called by
        :param:     plot_debug, if True do a debug plots
        :return:    None
        Acquire Data under a trigger mode, then save the Data to the HD.
        Get Constants from Constants.py
        Variables will be extracted from the Variables_dict who is managed by Disk_IO.
        When debug==True plot acquired data as diagnostic.
    """
    recall_pickled_dict(
    )  # Load variables from the c.s.v. c.pickle_txt_fullpath
    SamplingFreq = v.SamplingFreq  # Sampling Frequency, Hz
    TrigLevel = v.TrigLevel  # Actual Trigger level as a %
    AveragesRequired = v.AveragesRequired  # Required Averages number
    Length = v.Length  # Pile length
    Speed = v.Speed  # P wave speed in concrete
    T_echo_0 = 2 * Length / Speed  # Return time of echo_0
    indx_echo_0 = round(T_echo_0 *
                        SamplingFreq)  # Index of the 1.st echo_0 sample
    #
    save_actual_pid()  # Save the PID to a file, for successive kill
    #
    """ Called at each new Acquisition, remove files at c.temp_data_path, if the dir was removed, recreate it.
    """
    create_and_clear(c.temp_data_path, delete_files=True)
    #
    TrigLevel_32 = (
        TrigLevel / 100
    ) * c.full_scale_32  # From % to fraction of unity, to fract. of c.full_scale_32
    pk_foot = 1 / 100  # Relative factor for the pulse pedestal
    FORMAT = pyaudio.paInt32  # Signed 32 bit (Little Endian byte order)
    CHANNELS = int(2)
    byte_width = 4  # Bytes in one signed 32 bit sample
    #
    print()
    print("=" * 50)
    print("   Started '" + __file__ + "' as a separate executable")
    print("=" * 50)
    if c.IS_RASPBERRY: print("Machine is Raspberry Pi")
    else: print("Machine is not a Raspberry Pi")
    print("Audio device is:", c.SOUND_DEVICE)
    """ New error handler: suppress all libasound.so error messages, by substitution of the error handler. 
    """
    ERROR_HANDLER_FUNC = CFUNCTYPE(None, c_char_p, c_int, c_char_p, c_int,
                                   c_char_p)

    def py_error_handler(filename, line, function, err, fmt):
        print('.',
              end="")  # Print a single '.' point instead of the error message

    #   #
    #
    c_error_handler = ERROR_HANDLER_FUNC(py_error_handler)
    asound = cdll.LoadLibrary('libasound.so')
    #
    # Set error handler
    asound.snd_lib_error_set_handler(c_error_handler)
    #
    # Initialize PyAudio
    p = pyaudio.PyAudio()
    p.terminate()
    print()
    #
    #--------------------------- Error handler end ----------------------------------
    """ Creates an instance of the PyAudio class , and open a stream to write output frames
    """
    paudio = pyaudio.PyAudio()
    stream = paudio.open(rate=SamplingFreq,
                         channels=CHANNELS,
                         format=FORMAT,
                         input=True,
                         input_device_index=c.SOUND_DEVICE,
                         frames_per_buffer=c.N_FRAMES)
    #
    i = 1
    while i <= int(AveragesRequired):
        """ 
            Wait until the Trigger level is reached getting frames continuously,
            the format is long i.e. int32; the AD has 24 bit, 8 MSBits are zeros.
            The Trigger Channel is Ch1: Odd samples: 1, 3, 5,... => Ch1
            The variable inp_short_buffer cannot be predefined as a bytearray (that is mutable) 
            because stream.read() return an array of bytes (that is immutable).
        """
        inp_old_buffer = np.zeros(CHANNELS * c.N_FRAMES, dtype=np.int32)
        inp_big_buffer = np.zeros(CHANNELS * c.N_FRAMES * c.N_BLOCKS,
                                  dtype=np.int32)
        beg = 0
        end = CHANNELS * c.N_FRAMES
        os.system("sync")  # To empty write queue
        #
        # ====================== Trigger loop Begin ======================\
        #
        print("\n" + "=o" * 25 + "=")
        if do_plot: time.sleep(2)  # For testing, wait to close the plot window
        else: time.sleep(1)
        print("Waiting for Trigger n.", i, "...")
        while True:
            inp_short_buffer = np.frombuffer(stream.read(c.N_FRAMES), np.int32)

            #   #
            """ From the array of bytes inp_short_buffer of int32: ch1, ch2, c1, ch2,... blocks 
                of interleaved 4 bytes get the tuple ch1_int32 discarding each successive 4 bytes 
                represented by xxxx (corresponding to ch2, i.e. get only ch1 values), for a total of c.n_frames.
            """
            if len(inp_short_buffer) != c.N_FRAMES * CHANNELS: continue
            ch1_int32 = unpack(
                'ixxxx' * c.N_FRAMES, inp_short_buffer
            )  # Get tuple (i32 ch1 only) from tuple of bytes
            ch1_arr_32 = np.array(ch1_int32)  # From tuple to np array
            pk1_32 = np.max(
                np.abs(ch1_arr_32))  # Get absolute peak value of ch1 as int32
            if pk1_32 < TrigLevel_32:  # No Trigger
                inp_old_buffer = inp_short_buffer  # No Trigger, save as a possible pre-trigger
                continue
            else:  # Get Trigger
                """ The following print is only for testing: must been leaved commented.
                    Represents values of the trigger frame.
                """
                # rms1_32     = np.std(ch1_arr_32)                      # Check for single or few noise spike on ch1
                # print("Trig. rms1 = {:3.1f}".format((rms1_32 / c.full_scale_32)*1000), "mV")
                # print("Trig. pk1  = {:3.1f}".format((pk1_32  / c.full_scale_32)*1000), "mV")

                inp_big_buffer[
                    beg:end] = inp_old_buffer  # Pre-Trigger block of data
                beg = end  # Prepare 2.nd block address
                end += CHANNELS * c.N_FRAMES  # Next step into inp_big_buffer
                inp_big_buffer[
                    beg:
                    end] = inp_short_buffer  # Insert 2.nd block of Trigger data
                break
            #   #
        #   #
        # END while True:
        # ======================= Trigger loop End =======================/
        #
        # ====================== Acquisition loop start ==================\
        j = 2  # Block index: 0 and 1 blocks inserted inside the Trigger loop
        while j < c.N_BLOCKS:
            beg = end
            end += CHANNELS * c.N_FRAMES
            inp_short_buffer = np.frombuffer(stream.read(c.N_FRAMES), np.int32)
            inp_big_buffer[beg:end] = inp_short_buffer  # Packing blocks
            j += 1
        #   #
        # ====================== Acquisition loop end ====================/
        #
        """ Unpacking bytes to integers (32 bit) in the tuple tpl_i32. 
            Then separating interleaved Channels, Ch1, Ch2, getting c.N_of_samples points for channel.
            Finally we have y1, y2 as np.arrays of float64, and scaled to +/-1 and compensated for Pisound Input Gain.
            To verify the correctness of the formatting string: calcsize('ii') returns 8
        """
        tpl_i32 = unpack(
            'ii' * c.N_OF_SAMPLES,
            inp_big_buffer)  # Packed bytes and interleaved: Ch1, Ch2
        y1_tpl_i32 = tpl_i32[0:c.N_OF_SAMPLES *
                             CHANNELS:2]  # Odd:  Ch1 as integer 32
        y2_tpl_i32 = tpl_i32[1:c.N_OF_SAMPLES * CHANNELS +
                             1:2]  # Even: Ch2 as integer 32
        y1 = np.array(
            y1_tpl_i32
        ) / c.full_scale_32  # Scaled to +/- 1 full scale                                      # Go to float 8 bytes
        y2 = np.array(y2_tpl_i32) / c.full_scale_32
        y1 /= PI_INP_GAIN  # Pisound board Input Gain compensation
        y2 /= PI_INP_GAIN
        #

        # /\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
        # PUT A PULSE ON Ch1 TO TEST GLITCH DETECTION
        #
        # noise_1 = np.random.rand(np.max(y1.shape)) / 500
        # noise_2 = np.random.rand(np.max(y1.shape)) / 500
        # y1      = noise_1
        # y2      = noise_2
        # y1[480] = 0.5
        #
        # /\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\

        # ========================== SATURATION CHECK BEGIN ==========================\
        #
        y1_Min = np.min(y1)  # Getting min / max
        y1_Max = np.max(y1)
        y2_Min = np.min(y2)
        y2_Max = np.max(y2)
        max_abs1 = max(abs(y1_Min), abs(y1_Max))  # ch1 absolute peak
        max_abs2 = max(abs(y2_Min), abs(y2_Max))  # ch2 absolute peak
        max_abs = max(max_abs1, max_abs2)  # Greater absolute maximum peak
        #
        if max_abs < c.satur_threshold:  # Saturation check
            is_Saturated = False
        else:
            is_Saturated = True
            doBeep(on_list=alarm_3_S[0], off_list=alarm_3_S[1])
            print(Saturation_msg, "max_abs =", max_abs)
        #   #
        # ========================== SATURATION CHECK END ============================/

        # ========================== GLITCH TESTING BEGIN ============================\
        #
        W = 1  # Range minimum width
        is_err1 = False
        is_err2 = False
        #----------- NEW TEST BASED ON THE DIFFERENTIATION OF THE GLITCH (DIRAC DELTA FUNCTION)
        y1_d = np.diff(y1)
        y2_d = np.diff(y2)
        indx1 = min(min(
            np.nonzero(np.abs(y1) == max_abs1)))  # Index of max(abs(y1))
        indx2 = min(min(
            np.nonzero(np.abs(y2) == max_abs2)))  # Index of max(abs(y2))
        if indx1 < (np.max(y1.shape) - 2) and (np.max(
                abs(y1_d[indx1 - 1] - max_abs1)) < max_abs1 / 50):
            is_err1 = True
        #   #
        if indx2 < (np.max(y2.shape) - 2) and (np.max(
                abs(y2_d[indx1 - 1] - max_abs1)) < max_abs2 / 50):
            is_err2 = True
        #   #
        #-------------------------------------------------------------------------------
        print("")
        if is_err1: print(Glitch_msg, "Ch1")
        if is_err2: print(Glitch_msg, "Ch2")
        if is_err1 or is_err2:  # Glitches counted as saturation:
            doBeep(on_list=alarm_1_S[0], off_list=alarm_1_S[1])
            is_Saturated = True  #   the acquisition will be repeated
        #   #
        # ============================ GLITCH TESTING END ============================/

        # ======================= CHANNELS SWAP TESTING BEGIN ========================\
        #
        """ Sometime the first acquisition can swap ch1 with ch2, here we detect that occurrence, and in the case, 
            we recovery by a swap between ch1 and ch2. 
            1) The autocorrelations of y1 and y2 are calculated.
            2) The max absolute peaks of the autocorrelations are calculated.
            3) Each one of the autocorrelation amplitudes are normalized using the absolute peaks of point (2).
            4) The normalized autocorrelations are rounded to 1 decimal to exclude little oscillations around 0.
            5) The differentiation of the sign changements of the normalized and rounded correlations is calculated.
            6) The differentiations of point (5) are cumulated to obtain the final scalar count.
            7) The ch1 Force signal is a well behaved and then bandlimited pulse with only two zero crossing 
              (excluding noise), by contrast the ch2 Accel signal being a derivative (accelerometer signal) 
              has surely more sign changements, consequently when the total sign changes of ch1 exceeds that of ch2 
              the channels are swapped. 
        """
        upper = int(3 * indx_echo_0)  # Upper index limit for checking
        corr_y1 = correlate(y1[0:upper], y1[0:upper], mode='same')
        corr_y2 = correlate(y2[0:upper], y2[0:upper], mode='same')
        pk_abs_corr_1 = np.max(np.abs(corr_y1))
        pk_abs_corr_2 = np.max(np.abs(corr_y2))
        corr_y1 = np.round(corr_y1 / pk_abs_corr_1, 1)
        corr_y2 = np.round(corr_y2 / pk_abs_corr_2, 1)
        nz_diff_corr_y1 = (np.diff(np.sign(corr_y1), n=1) != 0).sum()
        nz_diff_corr_y2 = (np.diff(np.sign(corr_y2), n=1) != 0).sum()
        #
        if nz_diff_corr_y1 < nz_diff_corr_y2:
            is_swapped = False
        else:
            is_swapped = True
        #   #
        if plot_debug:
            print("\n" + "Zero crossing comparison:", nz_diff_corr_y1, " <  ",
                  nz_diff_corr_y2, " ", not is_swapped)
        #   #
        print("            Good if:")
        print(" nz_diff_corr_y1 < nz_diff_corr_y2")
        print("-----------------------------------")
        print("       ", nz_diff_corr_y1, "       |       ", nz_diff_corr_y2)
        print("-----------------------------------")
        if is_swapped:
            print("\n" + "/" * 35)
            print("     ERROR: Swapped ch1, ch2")
            print("        CORRECTION DONE!")
            print("/" * 35 + "\n")
            y1, y2 = y2, y1  # Restore from swap error
        #   #
        # ======================== CHANNELS SWAP TESTING END =========================/

        if not is_Saturated:
            """ If is_Saturated returns to the external acquisition loop: while i <= int(AveragesRequired)
                Preparing good data for storage:
                Relocation of the start of the inputrms1_32 signal in y1, y2, to eliminate the pre-trigger 
                part that has a variable length. That part will be shortened as follows: 
                a)  Search the first index where the Pulse input Data array (y1) is bigger than pk * pk_foot. 
                    The index of that basement point will be named index_Trig.
                b)  The new starting index is obtained decreasing, as possible, index_Trig by a fixed amount, 
                    in order to assure the inclusion of sufficient initial part of the signal. 
                    The corresponding amount of time, guard_T, will be equal to the hammer 
                    pulse width t.HAMMER_DT, the number of samples will be N_guard. 
            """
            y1_shape = y1.shape  # Array y1 dimensions
            y2_shape = y2.shape  # Array y2 dimensions
            if not (np.max(y1_shape) == np.max(y2_shape)):  # Length comparison
                print(Length_msg)
                print("get_audio: y1_shape =", y1_shape)
                print("get_audio: y2_shape =", y2_shape)
                doBeep(on_list=bip_1_5_S[0], off_list=bip_1_5_S[1])
                return
            #   #
            # ===============================  Relocation Begin =========================\
            #
            pk1 = pk1_32 / c.full_scale_32  # Peak scaled to +/- 1
            y1_length = np.max(y1_shape)  # Length of y1 array
            indexes = np.array(np.nonzero(
                abs(y1) > pk1 *
                pk_foot))[0, :]  # Array of indexes, [0,:] => from 1xN to N
            print("len(indexes) =", len(indexes))
            index_Trig = np.amin(
                indexes)  # Lowest index i.e. index pedestal start
            dT = 1.0 / SamplingFreq  # Sampling time
            guard_T = HAMMER_DT  # Safety margin time, before Pulse pedestal
            N_guard = int(round(guard_T /
                                dT))  # Corresponding guard margin as samples
            if index_Trig > N_guard:  # Guard must begin after y1[0]
                new_start = index_Trig - N_guard  # Index (>=0) for the new starting point
                tmp_array = np.zeros(
                    y1_length)  # Temporary empty array for new y1
                tmp_array[0:-1 - new_start] = copy.copy(
                    y1[new_start:-1]
                )  # Fill y1 without the guard section of y1
                # leaves a tail of zeros
                y1 = copy.copy(tmp_array)  # y1 replaced by relocated data
                tmp_array = np.zeros(np.max(
                    y2.shape))  # Temporary empty array for new y2
                tmp_array[0:-1 - new_start] = copy.copy(
                    y2[new_start:-1]
                )  # Fill y2 without the guard section of y2
                y2 = copy.copy(tmp_array)  # y2 replaced
                tmp_length = np.max(y1.shape)  # Actual length of y1 array
                if tmp_length != y1_length:
                    print(
                        "\n" +
                        "ERROR in get_audio: Length of y1, y2 modified erroneously"
                    )
                #   #
            else:  # Leave the signal unaltered
                pass
            #   #
            #===============================  Relocation End ===========================/
            #
            pk1 = np.max(np.abs(y1))
            pk2 = np.max(np.abs(y2))
            rms1 = np.std(y1)
            rms2 = np.std(y2)
            print("\n" + "Saving data to Disc")
            print("rms1 = {:3.1f}".format(rms1 * 1000),
                  "mV;  pk1  = {:3.1f}".format(pk1 * 1000), "mV")
            print("rms2 = {:3.1f}".format(rms2 * 1000),
                  "mV;  pk2  = {:3.1f}".format(pk2 * 1000), "mV")
            """ Save data to disc
            """
            name_1 = "/y1_" + str('{:02d}'.format(i))
            name_2 = "/y2_" + str('{:02d}'.format(i))
            path_1 = c.temp_data_path + name_1
            path_2 = c.temp_data_path + name_2
            #
            if c.sav_mode == "npy":
                """ Saving in Numpy binary file, not compressed, add extension .npy 
                """
                np.save(path_1, y1)  # Uncompressed format
                np.save(path_2, y2)
            elif c.sav_mode == "npz":
                """ Saving in Numpy binary file, compressed, add extension .npz 
                """
                np.savez_compressed(path_1, y1=y1)  # Compressed format
                np.savez_compressed(path_2, y2=y2)
            #   #
            elif c.sav_mode == "mat":
                """ Saving in Matlab format V.5, compressed, add automatically the extension .mat
                    loadmat() will return the vector in a dictionary ex:
                    d    = scipy.io.loadmat("vector.mat)
                    vect = d["vect"]
                    vect = d1["y1"]
                    and with one more dimension added ("column"):
                    original vect.shape -> (n,), returned vect.shape -> (n, 1); 
                    to return to the original dimension:
                    vect_reshaped = vect[:,0]
                """
                scipy.io.savemat(path_1, {"y1": y1},
                                 do_compression=True,
                                 oned_as="column")
                scipy.io.savemat(path_2, {"y2": y2},
                                 do_compression=True,
                                 oned_as="column")
            #   #
            print("New acquired data:", name_1, name_2)
            print("=o" * 25 + "=")
            save_blows(c.blows_list_txt, name_1, name_2)
            os.system("sync")  # Delayed write causes a random delay
            time.sleep(0.2)  # Delays for x seconds
            if plot_debug:
                sel = int(15 * indx_echo_0)  # was 1.5
                mx1 = max(np.abs(y1[0:sel]))
                mx2 = max(np.abs(y2[0:sel]))
                mx = 1.1 * max(mx1, mx2)
                tp = np.arange(
                    c.N_OF_SAMPLES) * 1.0 / SamplingFreq  # Time vector
                fig = plt.figure(1)
                fig.set_size_inches(w=3, h=4)
                fig.subplots_adjust(hspace=0.50)
                plt.subplot(211)
                plt.plot(tp[0:sel], y1[0:sel])
                plt.title('get_audio Hammer')
                plt.xlabel('Time [s]')
                plt.ylabel('Force')
                plt.grid(True)
                plt.axis([tp[0], tp[sel], -mx, mx])
                #
                plt.subplot(212)
                plt.plot(tp[0:sel], y2[0:sel])
                plt.title('get_audio Accel')
                plt.xlabel('Time [s]')
                plt.ylabel('Accel')
                plt.grid(True)
                plt.axis([tp[0], tp[sel], -mx, mx])
                #
                plt.show()
                time.sleep(0.1)
            # END if plot_debug:
            i += 1
        # END if not is_Saturated):
    # END while i <= int(AveragesRequired):
    #
    stream.stop_stream()
    stream.close()  # Close inp pyaudio object
    paudio.terminate()
    #
    print("get_audio: closed input device, terminated")