Beispiel #1
0
    def set_channel(self, channel):
        """
        Set a new Channel.

        """
        if channel is not None:
            self.chansil = ChannelSilence( channel, self.win_lenght )
        else:
            self.chansil = None
Beispiel #2
0
def search_channel_speech(channel, winlenght=0.010, minsildur=0.200, mintrackdur=0.300, shiftdurstart=0.010, shiftdurend=0.010 ):
    """
    Return a list of tracks (i.e. speech intervals where energy is high enough).
    Use only default parameters.

    @param channel (Channel - IN) The channel we'll try to find tracks
    @return A list of tuples (fromtime,totime)

    """
    chansil = ChannelSilence( channel, winlenght )
    chansil.search_silences( threshold=0, mintrackdur=0.08 )
    chansil.filter_silences( minsildur )
    tracks = chansil.extract_tracks( mintrackdur, shiftdurstart, shiftdurend )
    tracks.append( (channel.get_nframes(),channel.get_nframes()) )
    trackstimes = frames2times(tracks, channel.get_framerate())

    return trackstimes
    def SetChannel(self, newchannel):
        """
        Set a new channel, estimates the values to be displayed.

        """
        # Set the channel
        self._channel = newchannel

        wx.BeginBusyCursor()
        b = wx.BusyInfo("Please wait while loading and analyzing data...")

        # To estimate values related to amplitude
        frames = self._channel.get_frames(self._channel.get_nframes())
        self._ca = AudioFrames(frames, self._channel.get_sampwidth(), 1)

        # Estimates the RMS (=volume), then find where are silences, then IPUs
        self._cv = ChannelSilence(self._channel)
        self._cv.search_silences()               # threshold=0, mintrackdur=0.08
        self._cv.filter_silences()               # minsildur=0.2
        self._tracks = self._cv.extract_tracks() # mintrackdur=0.3

        b.Destroy()
        b = None
        wx.EndBusyCursor()
Beispiel #4
0
class IPUsAudio( object ):
    """
    @author:       Brigitte Bigi
    @organization: Laboratoire Parole et Langage, Aix-en-Provence, France
    @contact:      [email protected]
    @license:      GPL, v3
    @copyright:    Copyright (C) 2011-2016  Brigitte Bigi
    @summary:      An IPUs segmenter from audio.

    IPUs - Inter-Pausal Units are blocks of speech bounded by silent pauses
    of more than X ms, and time-aligned on the speech signal.

    """
    MIN_SIL_DUR = 0.08
    MIN_IPU_DUR = 0.08

    def __init__(self, channel):
        """
        Creates a new IPUsAudio instance.

        """
        super(IPUsAudio, self).__init__()
        self.reset()
        self.set_channel(channel)

    # ------------------------------------------------------------------

    def reset(self):
        """
        Set default values.

        """
        self.min_sil_dur   = 0.250
        self.min_ipu_dur   = 0.300
        self.vol_threshold = 0
        self.shift_start   = 0.010
        self.shift_end     = 0.020
        self.win_lenght    = 0.020
        self.auto_vol      = True

        self.bornestart = False
        self.borneend   = False

    # ------------------------------------------------------------------
    # Manage Channel
    # ------------------------------------------------------------------

    def get_channel(self):
        """
        Return the channel.

        """
        return self.chansil.get_channel()

    # ------------------------------------------------------------------

    def set_channel(self, channel):
        """
        Set a new Channel.

        """
        if channel is not None:
            self.chansil = ChannelSilence( channel, self.win_lenght )
        else:
            self.chansil = None

    # ------------------------------------------------------------------

    def reset_silences(self):
        """
        Reset the list of silences.

        """
        if self.chansil is not None:
            self.chansil.reset_silences()

    # ------------------------------------------------------------------

    def set_silences(self, silences):
        """
        Fix the list of silences.

        """
        if self.chansil is not None:
            self.chansil.set_silences( silences )


    # ------------------------------------------------------------------
    # Setters for members
    # ------------------------------------------------------------------

    def set_vol_threshold(self, vol_threshold):
        """
        Fix the default minimum volume value to find silences.

        @param vol_threshold (int) RMS value

        """
        self.vol_threshold = int(vol_threshold)
        if vol_threshold == 0:
            self.auto_vol = True
        else:
            self.auto_vol = False

    # ------------------------------------------------------------------

    def set_min_silence(self, min_sil_dur):
        """
        Fix the default minimum duration of a silence.

        @param min_sil_dur (float) Duration in seconds.

        """
        self.min_sil_dur = float(min_sil_dur)

    # ------------------------------------------------------------------

    def set_min_speech(self, min_ipu_dur):
        """
        Fix the default minimum duration of an IPU.

        @param min_ipu_dur (float) Duration in seconds.

        """
        self.min_ipu_dur = float(min_ipu_dur)

    # ------------------------------------------------------------------

    def set_vol_win_lenght(self, winlength):
        """
        Fix the default windows length for RMS estimations.

        @param winlength (float) Duration in seconds.

        """
        self.win_lenght = max(winlength, 0.005)

    # ------------------------------------------------------------------

    def set_shift(self, s):
        """
        Fix the default minimum boundary shift value.

        @param s (float) Duration in seconds.

        """
        self.shift_start = float(s)
        self.shift_end   = float(s)

    # ------------------------------------------------------------------

    def set_shift_start(self, s):
        """
        Fix the default minimum boundary shift value.

        @param s (float) Duration in seconds.

        """
        self.shift_start = float(s)

    # ------------------------------------------------------------------

    def set_shift_end(self,s):
        """
        Fix the default minimum boundary shift value.

        @param s (float) Duration in seconds.

        """
        self.shift_end = float(s)

    # ------------------------------------------------------------------

    def min_channel_duration(self):
        """
        Return the minimum duration we expect for a channel.

        """
        d1 = self.min_sil_dur+self.shift_start+self.shift_end
        d2 = self.min_ipu_dur+self.shift_start+self.shift_end
        return max(d1,d2)

    # ------------------------------------------------------------------

    def set_bound_start(self, sil=False):
        """
        Fix if it is expected (or not) to find a silence at the beginning of the channel.

        """
        self.bornestart = sil

    # ------------------------------------------------------------------

    def set_bound_end(self, sil=False):
        """
        Fix if it is expected (or not) to find a silence at the end of the channel.

        """
        self.borneend = sil

    # ------------------------------------------------------------------
    # Silence/Speech segmentation
    # ------------------------------------------------------------------

    def extract_tracks(self, min_ipu_dur=None, shift_start=None, shift_end=None):
        """
        Return a list of tuples (from_pos,to_pos) of tracks.
        The tracks are found from the current list of silences.

        @param min_ipu_dur (float) The minimum duration for a track (in seconds)
        @param shiftdurstart (float) The time to remove to the start boundary (in seconds)
        @param shiftdurend (float) The time to add to the end boundary (in seconds)
        @return (list of tuples)

        """
        if self.chansil is None:
            return []

        if min_ipu_dur is None:
            min_ipu_dur=self.min_ipu_dur
        if shift_start is None:
            shift_start=self.shift_start
        if shift_end is None:
            shift_end=self.shift_end

        return self.chansil.extract_tracks(min_ipu_dur, shift_start, shift_end)

    # ------------------------------------------------------------------

    def search_tracks(self, volume):
        """
        Return the tracks if volume is used as threshold.

        """
        if self.chansil is None:
            return []

        self.chansil.search_silences(volume, mintrackdur=IPUsAudio.MIN_IPU_DUR)
        self.chansil.filter_silences(self.min_sil_dur)
        return self.extract_tracks()

    # ------------------------------------------------------------------

    def check_boundaries(self, tracks):
        """
        Check if silences at start and end are as expected.

        @return bool

        """
        if len(tracks) == 0:
            return False
        if self.chansil is None:
            return False

        if self.bornestart is False and self.borneend is False:
            # we do not know anything about silences at start and end
            # then, everything is ALWAYS OK!
            return True

        first_from_pos = tracks[0][0]
        last_to_pos = tracks[len(tracks)-1][1]

        # If I expected a silence at start... and I found a track
        if self.bornestart is True and first_from_pos==0:
            return False

        # If I expected a silence at end... and I found a track
        if self.borneend is True and last_to_pos==self.chansil.get_channel().get_nframes():
            return False

        return True

    # ------------------------------------------------------------------

    def split_into_vol(self, nbtracks):
        """
        Try various volume values to estimate silences then get the expected number of tracks.

        @param nbtracks is the expected number of IPUs
        @return number of tracks

        """
        if self.chansil is None:
            return 0

        volstats = self.chansil.get_volstats()
        # Min volume in the speech
        vmin = volstats.min()
        # Max is set to the mean
        vmax = volstats.mean()
        # Step is necessary to not exaggerate a detailed search!
        # step is set to 5% of the volume between min and mean.
        step = int( (vmax - vmin) / 20.0 )
        # Min and max are adjusted
        vmin += step
        vmax -= step

        # First Test !!!
        self.vol_threshold = vmin
        tracks = self.search_tracks(vmin)
        n = len(tracks)
        b = self.check_boundaries(tracks)

        while (n != nbtracks or b is False):
            # We would never be done anyway.
            if (vmax==vmin) or (vmax-vmin) < step:
                return n

            # Try with the middle volume value
            vmid = int(vmin + (vmax - vmin) / 2.0)
            if n > nbtracks:
                # We split too often. Need to consider less as silence.
                vmax = vmid
            elif n < nbtracks:
                # We split too seldom. Need to consider more as silence.
                vmin = vmid
            else:
                # We did not find start/end silence.
                vmin += step

            # Find silences with these parameters
            self.vol_threshold = int(vmid)
            tracks = self.search_tracks(vmid)
            n = len(tracks)
            b = self.check_boundaries(tracks)

        return n

    # ------------------------------------------------------------------

    def split_into(self, nbtracks):
        """
        Try various volume values, pause durations and silence duration to get silences.

        @param nbtracks is the expected number of IPUs. 0=auto.

        """
        if self.chansil is None:
            raise Exception('No audio data.')

        if self.auto_vol is True:
            self.vol_threshold = self.chansil.search_threshold_vol()

        if nbtracks == 0:
            self.search_tracks( self.vol_threshold )
            return 0

        # Try with default parameters:
        tracks = self.search_tracks( self.vol_threshold )
        n = len(tracks)
        b = self.check_boundaries(tracks)

        if n == nbtracks and b is True:
            return n

        # Try with default lengths (change only volume):
        n = self.split_into_vol( nbtracks )

        if n > nbtracks:

            # We split too often. Try with larger' values.
            while n > nbtracks:
                self.min_sil_dur += self.win_lenght
                self.min_ipu_dur += self.win_lenght
                n = self.split_into_vol( nbtracks )

        elif n < nbtracks:

            # We split too seldom. Try with shorter' values of silences
            p = self.min_sil_dur
            m = self.min_ipu_dur
            while n < nbtracks and self.min_sil_dur > IPUsAudio.MIN_SIL_DUR:
                self.min_sil_dur -= self.win_lenght
                n = self.split_into_vol( nbtracks )

            # we failed... try with shorter' values of ipus
            if n < nbtracks:
                self.min_sil_dur = p
                while n < nbtracks and self.min_ipu_dur > IPUsAudio.MIN_IPU_DUR:
                    self.min_ipu_dur -= self.win_lenght
                    n = self.split_into_vol( nbtracks )

                # we failed... try with shorter' values of both sil/ipus
                if n < nbtracks:
                    self.min_ipu_dur = m
                    while n < nbtracks and self.min_sil_dur > IPUsAudio.MIN_SIL_DUR and self.min_ipu_dur > IPUsAudio.MIN_IPU_DUR:
                        self.min_ipu_dur -= self.win_lenght
                        self.min_sil_dur -= self.win_lenght
                        n = self.split_into_vol( nbtracks )

        return n
class AudioRoamerPanel( wx.Panel ):
    """
    @author:       Brigitte Bigi
    @organization: Laboratoire Parole et Langage, Aix-en-Provence, France
    @contact:      [email protected]
    @license:      GPL, v3
    @copyright:    Copyright (C) 2011-2016  Brigitte Bigi
    @summary:      Display info about a channel. Allows to save.

    This panel display all information about a channel:
      - amplitudes: nb of frames, min/max values, zero crossing,
      - clipping rates
      - volumes: min/max/mean
      - silence/speech automatic segmentation.

    Methods allow to save:
      - the channel or a fragment of the channel, in an audio file;
      - the information in a text file.

    """
    FRAMERATES = [ "16000", "32000", "48000" ]
    SAMPWIDTH  = [ "8", "16", "32" ]
    INFO_LABELS = {"framerate":("  Frame rate (Hz): ",FRAMERATES[0]),
                   "sampwidth":("  Samp. width (bits): ",SAMPWIDTH[0]),
                   "mul":      ("  Multiply values by: ","1.0"),
                   "bias":     ("  Add bias value: ","0"),
                   "offset":   ("  Remove offset value: ",False),
                   "nframes":  ("  Number of frames: "," ... "),
                   "minmax":   ("  Min/Max values: "," ... "),
                   "cross":    ("  Zero crossings: "," ... "),
                   "volmin":   ("  Volume min: "," ... "),
                   "volmax":   ("  Volume max: "," ... "),
                   "volavg":   ("  Volume mean: "," ... "),
                   "volsil":   ("  Threshold volume: "," ... "),
                   "nbipus":   ("  Number of IPUs: "," ... "),
                   "duripus":  ("  Nb frames of IPUs: "," ... ")
                   }

    def __init__(self, parent, preferences, channel):
        """
        Create a new AudioRoamerPanel instance.

        @param parent (wxWindow)
        @param preferences (structs.Preferences)
        @param channel (audiodata.Channel)

        """
        wx.Panel.__init__(self, parent)
        self._channel  = channel  # Channel
        self._filename = None     # Fixed when "Save as" is clicked
        self._cv = None           # ChannelSilence, fixed by ShowInfos
        self._tracks = None       # the IPUs we found automatically
        self._ca = None           # AudioFrames with only this channel, fixed by ShowInfos
        self._wxobj = {}          # Dict of wx objects

        sizer = self._create_content()

        self.MODIFIABLES = {}
        for key in ["framerate","sampwidth","mul","bias","offset"]:
            self.MODIFIABLES[key] = AudioRoamerPanel.INFO_LABELS[key][1]

        self.SetPreferences(preferences)
        self.SetSizer(sizer)
        self.SetAutoLayout( True )
        self.SetMinSize((MIN_PANEL_W,MIN_PANEL_H))
        self.Layout()

    # -----------------------------------------------------------------------
    # Private methods to show information about the channel into the GUI.
    # -----------------------------------------------------------------------

    def _create_content(self):
        """
        Create the main sizer, add content then return it.

        """
        sizer = wx.BoxSizer(wx.HORIZONTAL)

        info = self._create_content_infos()
        clip = self._create_content_clipping()
        ipus = self._create_content_ipus()

        sizer.AddSpacer(5)
        sizer.Add(info, 1, wx.EXPAND, 0)
        sizer.AddSpacer(10)
        sizer.Add(clip, 0, wx.ALL, 0)
        sizer.AddSpacer(10)
        sizer.Add(ipus, 1, wx.EXPAND, 0)
        sizer.AddSpacer(5)

        return sizer

    def _create_content_infos(self):
        """
        GUI design for amplitude and volume information.

        """
        gbs = wx.GridBagSizer(10, 2)

        static_tx = wx.StaticText(self, -1, "Amplitude values: ")
        gbs.Add(static_tx, (0,0), (1,2), flag=wx.LEFT, border=2)
        self._wxobj["titleamplitude"] = (static_tx,None)

        self.__add_info(self, gbs, "nframes", 1)
        self.__add_info(self, gbs, "minmax",  2)
        self.__add_info(self, gbs, "cross",   3)

        static_tx = wx.StaticText(self, -1, "")
        gbs.Add(static_tx, (4,0), (1,2), flag=wx.LEFT, border=2)

        cfm = wx.ComboBox(self, -1, choices=AudioRoamerPanel.FRAMERATES, style=wx.CB_READONLY)
        cfm.SetMinSize((120,24))
        self.__add_modifiable(self, gbs, cfm, "framerate", 5)
        self.Bind(wx.EVT_COMBOBOX, self.OnModif, cfm)

        csp = wx.ComboBox(self, -1, choices=AudioRoamerPanel.SAMPWIDTH, style=wx.CB_READONLY)
        csp.SetMinSize((120,24))
        self.__add_modifiable(self, gbs, csp, "sampwidth", 6)
        self.Bind(wx.EVT_COMBOBOX, self.OnModif, csp)

        txm = wx.TextCtrl(self, -1, AudioRoamerPanel.INFO_LABELS["mul"][1], validator=TextAsNumericValidator())
        txm.SetInsertionPoint(0)
        self.__add_modifiable(self, gbs, txm, "mul", 7)
        self.Bind(wx.EVT_TEXT_ENTER , self.OnModif, txm)

        txb = wx.TextCtrl(self, -1, AudioRoamerPanel.INFO_LABELS["bias"][1], validator=TextAsNumericValidator())
        txb.SetInsertionPoint(0)
        self.__add_modifiable(self, gbs, txb, "bias", 8)
        self.Bind(wx.EVT_TEXT_ENTER, self.OnModif, txb)

        cb = wx.CheckBox(self, -1, style=wx.NO_BORDER)
        cb.SetValue( AudioRoamerPanel.INFO_LABELS["offset"][1] )
        self.__add_modifiable(self, gbs, cb, "offset", 9)
        self.Bind(wx.EVT_CHECKBOX, self.OnModif, cb)

        gbs.AddGrowableCol(1)

        border = wx.BoxSizer()
        border.Add(gbs, 1, wx.ALL | wx.EXPAND, 10)
        return border

    def _create_content_clipping(self):
        """
        GUI design for clipping information.

        """
        gbs = wx.GridBagSizer(11, 2)

        static_tx = wx.StaticText(self, -1, "Clipping rates:")
        gbs.Add(static_tx, (0,0), (1,2), flag=wx.LEFT, border=2)
        self._wxobj["titleclipping"] = (static_tx,None)

        for i in range(1,10):
            self.__add_clip(self, gbs, i)

        border = wx.BoxSizer()
        border.Add(gbs, 1, wx.ALL | wx.EXPAND, 10)
        return border

    def _create_content_ipus(self):
        """
        GUI design for information about an IPUs segmentation...

        """
        gbs = wx.GridBagSizer(9, 2)

        static_tx = wx.StaticText(self, -1, "Root-mean square:")
        gbs.Add(static_tx, (0,0), (1,2), flag=wx.LEFT, border=2)
        self._wxobj["titlevolume"] = (static_tx,None)

        self.__add_info(self, gbs, "volmin", 1)
        self.__add_info(self, gbs, "volmax", 2)
        self.__add_info(self, gbs, "volavg", 3)

        static_tx = wx.StaticText(self, -1, "")
        gbs.Add(static_tx, (4, 0), (1,2), flag=wx.LEFT, border=2)

        static_tx = wx.StaticText(self, -1, "Automatic detection of silences:")
        gbs.Add(static_tx, (5, 0), (1,2), flag=wx.LEFT, border=2)
        self._wxobj["titleipus"] = (static_tx,None)

        self.__add_info(self, gbs, "volsil",  6)
        self.__add_info(self, gbs, "nbipus",  7)
        self.__add_info(self, gbs, "duripus", 8)

        border = wx.BoxSizer()
        border.Add(gbs, 1, wx.ALL | wx.EXPAND, 10)
        return border

    # -----------------------------------------------------------------------
    # Callbacks to events
    # -----------------------------------------------------------------------

    def OnModif(self, evt):
        """
        Callback on a modifiable object: adapt foreground color.

        """
        evtobj   = evt.GetEventObject()
        evtvalue = evtobj.GetValue()
        for (key,defaultvalue) in self.MODIFIABLES.iteritems():
            (tx,obj) = self._wxobj[key]
            if evtobj == obj:
                if evtvalue == defaultvalue:
                    obj.SetForegroundColour( self._prefs.GetValue('M_FG_COLOUR') )
                    tx.SetForegroundColour( self._prefs.GetValue('M_FG_COLOUR') )
                else:
                    obj.SetForegroundColour( INFO_COLOUR )
                    tx.SetForegroundColour( INFO_COLOUR )
                obj.Refresh()
                tx.Refresh()
                return

    # -----------------------------------------------------------------------
    # Setters for GUI
    # ----------------------------------------------------------------------

    def SetPreferences(self, prefs):
        """
        Set new preferences. Refresh GUI.

        """
        self._prefs = prefs
        self.SetFont( prefs.GetValue('M_FONT') )
        self.SetBackgroundColour( prefs.GetValue('M_BG_COLOUR') )
        self.SetForegroundColour( prefs.GetValue('M_FG_COLOUR') )
        self.Refresh()

    # ----------------------------------------------------------------------

    def SetFont(self, font):
        """
        Change font of all wx texts.

        """
        wx.Window.SetFont( self, font )
        for (tx,obj) in self._wxobj.values():
            tx.SetFont(font)
            if obj is not None:
                obj.SetFont(font)
            else:
                # a title (make it bold)
                newfont = wx.Font(font.GetPointSize(), font.GetFamily(), font.GetStyle(), wx.BOLD, False, font.GetFaceName(), font.GetEncoding())
                tx.SetFont(newfont)

    # ----------------------------------------------------------------------

    def SetBackgroundColour(self, color):
        """
        Change background of all texts.

        """
        wx.Window.SetBackgroundColour( self, color )
        for (tx,obj) in self._wxobj.values():
            tx.SetBackgroundColour( color )
            if obj is not None:
                obj.SetBackgroundColour( color )

    # ----------------------------------------------------------------------

    def SetForegroundColour(self, color):
        """
        Change foreground of all texts.

        """
        wx.Window.SetForegroundColour( self, color )
        for (tx,obj) in self._wxobj.values():
            tx.SetForegroundColour( color )
            if obj is not None:
                obj.SetForegroundColour( color )

    # ----------------------------------------------------------------------
    # Methods of the workers
    # ----------------------------------------------------------------------

    def ShowInfo(self):
        """
        Estimate all values then display the information.

        """
        # we never estimated values. we have to do it!
        if self._cv is None:
            try:
                self.SetChannel(self._channel)
            except Exception as e:
                ShowInformation(self, self._prefs, "Error: %s"%str(e))
                return

        # Amplitude
        self._wxobj["nframes"][1].ChangeValue( " "+str(self._channel.get_nframes())+" " )
        self._wxobj["minmax"][1].ChangeValue( " "+str(self._ca.minmax())+" " )
        self._wxobj["cross"][1].ChangeValue( " "+str(self._ca.cross())+" " )

        # Modifiable
        fm = str(self._channel.get_framerate())
        if not fm in AudioRoamerPanel.FRAMERATES:
            self._wxobj["framerate"][1].Append( fm )
        self._wxobj["framerate"][1].SetStringSelection(fm)
        self.MODIFIABLES["framerate"] = fm

        sp = str(self._channel.get_sampwidth()*8)
        if not sp in AudioRoamerPanel.SAMPWIDTH:
            self._wxobj["sampwidth"][1].Append( sp )
        self._wxobj["sampwidth"][1].SetStringSelection( sp )
        self.MODIFIABLES["sampwidth"] = sp

        # Clipping
        for i in range(1,10):
            cr = self._ca.clipping_rate( float(i)/10. ) * 100.
            self._wxobj["clip"+str(i)][1].ChangeValue( " "+str( round(cr,2))+"% ")

        # Volumes / Silences
        vmin = self._cv.get_volstats().min()
        vmax = self._cv.get_volstats().max()
        vavg = self._cv.get_volstats().mean()
        self._wxobj["volmin"][1].ChangeValue( " "+str(vmin)+" ("+str(amp2db(vmin))+" dB) " )
        self._wxobj["volmax"][1].ChangeValue( " "+str(vmax)+" ("+str(amp2db(vmax))+" dB) " )
        self._wxobj["volavg"][1].ChangeValue( " "+str(int(vavg) )+" ("+str(amp2db(vavg))+" dB) ")
        self._wxobj["volsil"][1].ChangeValue( " "+str(self._cv.search_threshold_vol())+" " )
        self._wxobj["nbipus"][1].ChangeValue( " "+str(len(self._tracks))+" " )
        d = sum( [(e-s) for (s,e) in self._tracks] )
        self._wxobj["duripus"][1].ChangeValue( " "+str(d)+" " )

    # -----------------------------------------------------------------------

    def SetChannel(self, newchannel):
        """
        Set a new channel, estimates the values to be displayed.

        """
        # Set the channel
        self._channel = newchannel

        wx.BeginBusyCursor()
        b = wx.BusyInfo("Please wait while loading and analyzing data...")

        # To estimate values related to amplitude
        frames = self._channel.get_frames(self._channel.get_nframes())
        self._ca = AudioFrames(frames, self._channel.get_sampwidth(), 1)

        # Estimates the RMS (=volume), then find where are silences, then IPUs
        self._cv = ChannelSilence(self._channel)
        self._cv.search_silences()               # threshold=0, mintrackdur=0.08
        self._cv.filter_silences()               # minsildur=0.2
        self._tracks = self._cv.extract_tracks() # mintrackdur=0.3

        b.Destroy()
        b = None
        wx.EndBusyCursor()

    # -----------------------------------------------------------------------

    def ApplyChanges(self, from_time=None, to_time=None):
        """
        Return a channel with changed applied.

        @param from_time (float)
        @param to_time (float)
        @return (Channel) new channel or None if nothing changed

        """
        # Get the list of modifiable values from wx objects
        fm     = int(self._wxobj["framerate"][1].GetValue())
        sp     = int(int(self._wxobj["sampwidth"][1].GetValue())/8)
        mul    = float(self._wxobj["mul"][1].GetValue())
        bias   = int(self._wxobj["bias"][1].GetValue())
        offset = self._wxobj["offset"][1].GetValue()

        dirty = False
        if from_time is None:
            from_frame = 0
        else:
            from_frame = int( from_time * fm )
            dirty = True
        if to_time is None:
            to_frame = self._channel.get_nframes()
        else:
            dirty = True
            to_frame = int(to_time * fm)

        channel = self._channel.extract_fragment(from_frame,to_frame)

        # If something changed, apply this/these change-s to the channel
        if fm != self._channel.get_framerate() or sp != self._channel.get_sampwidth() or mul != 1. or bias != 0 or offset is True:
            wx.BeginBusyCursor()
            b = wx.BusyInfo("Please wait while formatting data...")
            channelfmt = ChannelFormatter( channel )
            channelfmt.set_framerate(fm)
            channelfmt.set_sampwidth(sp)
            channelfmt.convert()
            channelfmt.mul(mul)
            channelfmt.bias(bias)
            if offset is True:
                channelfmt.remove_offset()
            channel = channelfmt.get_channel()
            dirty = True
            b.Destroy()
            b = None
            wx.EndBusyCursor()

        if dirty is True:
            return channel
        return None

    # -----------------------------------------------------------------------

    def SaveChannel(self, parentfilename, period=False):
        """
        Save the channel in an audio file.

        @param parentfilename (str)
        @param period (bool) Save a portion of the channel only

        """
        s = None
        e = None
        if period is True:
            dlg = PeriodChooser( self, self._prefs, 0., float(self._channel.get_nframes())/float(self._channel.get_framerate()) )
            answer = dlg.ShowModal()
            if answer == wx.ID_OK:
                (s,e) = dlg.GetValues()
                try:
                    s = float(s)
                    e = float(e)
                    if e < s: raise Exception
                except Exception:
                    ShowInformation( self, self._prefs, "Error in the definition of the portion of time.", style=wx.ICON_ERROR)
                    return
            dlg.Destroy()
            if answer != wx.ID_OK:
                return

        newfilename = SaveAsAudioFile()

        # If it is the OK response, process the data.
        if newfilename is not None:
            if newfilename == parentfilename:
                ShowInformation( self, self._prefs, "Assigning the current file name is forbidden. Choose a new file name.", style=wx.ICON_ERROR)
                return

            # Create a formatted channel
            try:
                channel = self.ApplyChanges(s,e)
            except Exception as e:
                ShowInformation( self, self._prefs, "Error while formatting the channel: %s"%str(e), style=wx.ICON_ERROR)
                return

            message = "File %s saved successfully."%newfilename
            if channel is None:
                channel = self._channel
            else:
                message +="\nYou can now open it with AudioRoamer to see your changes!"

            # Save the channel
            try:
                audio = AudioPCM()
                audio.append_channel(channel)
                audiodata.io.save(newfilename, audio)
            except Exception as e:
                message = "File not saved. Error: %s"%str(e)
            else:
                # Update members
                self._filename = newfilename

            ShowInformation( self, self._prefs, message, style=wx.ICON_ERROR)

    # -----------------------------------------------------------------------

    def SaveInfos(self, parentfilename):
        """
        Ask for a filename then save all displayed information.

        """
        newfilename = SaveAsAnyFile()
        # If it is the OK response, process the data.
        if newfilename is not None:
            content = self._infos_content(parentfilename)
            with codecs.open(newfilename, "w", encoding) as fp:
                fp.write(content)

    # -----------------------------------------------------------------------
    # Private methods to list information in a "formatted" text.
    # -----------------------------------------------------------------------

    def _infos_content(self, parentfilename):
        content  = ""
        content += self.__separator()
        content += self.__line(program + ' - Version ' + version)
        content += self.__line(copyright)
        content += self.__line("Web site: "+ url)
        content += self.__line("Contact: "+ author + "("+ contact + ")")
        content += self.__separator()
        content += self.__newline()
        content += self.__line("Date: " + str(datetime.datetime.now()))

        # General information
        content += self.__section("General information")
        content += self.__line("Channel filename: %s"%self._filename)
        content += self.__line("Channel extracted from file: "+parentfilename)
        content += self.__line("Duration: %s sec."%self._channel.get_duration())
        content += self.__line("Framerate: %d Hz"%self._channel.get_framerate())
        content += self.__line("Samp. width: %d bits"%(int(self._channel.get_sampwidth())*8))

        # Amplitude
        content += self.__section("Amplitude")
        content += self.__line(AudioRoamerPanel.INFO_LABELS["nframes"][0]+self._wxobj["nframes"][1].GetValue())
        content += self.__line(AudioRoamerPanel.INFO_LABELS["minmax"][0]+self._wxobj["minmax"][1].GetValue())
        content += self.__line(AudioRoamerPanel.INFO_LABELS["cross"][0]+self._wxobj["cross"][1].GetValue())

        # Clipping
        content += self.__section("Amplitude clipping")
        for i in range(1,10):
            f = self._ca.clipping_rate( float(i)/10. ) * 100.
            content += self.__item("  factor "+str(float(i)/10.)+": "+str(round(f,2))+"%")

        # Volume
        content += self.__section("Root-mean square")
        content += self.__line(AudioRoamerPanel.INFO_LABELS["volmin"][0]+self._wxobj["volmin"][1].GetValue())
        content += self.__line(AudioRoamerPanel.INFO_LABELS["volmax"][0]+self._wxobj["volmax"][1].GetValue())
        content += self.__line(AudioRoamerPanel.INFO_LABELS["volavg"][0]+self._wxobj["volavg"][1].GetValue())

        # IPUs
        content += self.__section("Inter-Pausal Units automatic segmentation")
        content += self.__line(AudioRoamerPanel.INFO_LABELS["volsil"][0]+self._wxobj["volsil"][1].GetValue())
        content += self.__line(AudioRoamerPanel.INFO_LABELS["nbipus"][0]+self._wxobj["nbipus"][1].GetValue())
        content += self.__line(AudioRoamerPanel.INFO_LABELS["duripus"][0]+self._wxobj["duripus"][1].GetValue())
        content += self.__newline()
        content += self.__separator()

        return content

    # -----------------------------------------------------------------------
    # Private methods.
    # -----------------------------------------------------------------------

    def __add_info(self, parent, gbs, key, row):
        """ Private method to add an info into the GridBagSizer. """
        static_tx = wx.StaticText(parent, -1, AudioRoamerPanel.INFO_LABELS[key][0])
        gbs.Add(static_tx, (row, 0), flag=wx.ALIGN_CENTER_VERTICAL|wx.LEFT, border=2)
        tx = wx.TextCtrl(parent, -1, AudioRoamerPanel.INFO_LABELS[key][1], style=wx.TE_READONLY)
        tx.SetMinSize((120,24))
        gbs.Add(tx, (row, 1), flag=wx.ALIGN_CENTER_VERTICAL|wx.LEFT, border=2)
        self._wxobj[key] = (static_tx,tx)

    def __add_clip(self, parent, gbs, i):
        """ Private method to add a clipping value in a GridBagSizer. """
        static_tx = wx.StaticText(parent, -1, "  factor "+str( float(i)/10.)+": " )
        gbs.Add(static_tx, (i, 0), flag=wx.ALIGN_CENTER_VERTICAL|wx.LEFT, border=2)
        tx = wx.TextCtrl(parent, -1, " ... ", style=wx.TE_READONLY|wx.TE_RIGHT)
        gbs.Add(tx, (i, 1), flag=wx.ALIGN_CENTER_VERTICAL|wx.RIGHT, border=2)
        self._wxobj["clip"+str(i)] = (static_tx,tx)

    def __add_modifiable(self, parent, gbs, obj, key, row):
        static_tx = wx.StaticText(parent, -1, AudioRoamerPanel.INFO_LABELS[key][0])
        #static_tx =  wx.TextCtrl(parent, -1, AudioRoamerPanel.INFO_LABELS[key][0], style=wx.TE_READONLY|wx.TE_LEFT|wx.NO_BORDER)
        gbs.Add(static_tx, (row, 0), flag=wx.ALIGN_CENTER_VERTICAL|wx.LEFT, border=2)
        gbs.Add(obj, (row, 1), flag=wx.ALIGN_CENTER_VERTICAL|wx.LEFT, border=2)
        self._wxobj[key] = (static_tx,obj)

    # -----------------------------------------------------------------------

    def __section(self, title):
        """ Private method to make to look like a title. """
        text  = self.__newline()
        text += self.__separator()
        text += self.__line(title)
        text += self.__separator()
        text += self.__newline()
        return text

    def __line(self, msg):
        """ Private method to make a text as a simple line. """
        text  = msg.strip()
        text += self.__newline()
        return text

    def __item(self, msg):
        """ Private method to make a text as a simple item. """
        text  = "  - "
        text += self.__line(msg)
        return text

    def __newline(self):
        """ Private method to return a new empty line. """
        if wx.Platform == '__WXMAC__' or wx.Platform == '__WXGTK__':
            return "\n"
        return "\r\n"

    def __separator(self):
        """ Private method to return a separator line. """
        text  = "-----------------------------------------------------------------"
        text += self.__newline()
        return text