예제 #1
0
    def __init__(self, channel, winlen=0.01):
        """
        Constructor.

        @param channel (Channel) The channel to work on.
        @param winlen (float) Window length to estimate the volume.

        """
        BaseVolume.__init__(self)
        self.winlen = winlen

        # Remember current position
        pos = channel.tell()

        # Rewind to the beginning
        channel.rewind()

        # Constants
        nbframes = int(winlen * channel.get_framerate())
        nbvols = int(channel.get_duration()/winlen) + 1
        self.volumes = [0]*nbvols

        for i in range(nbvols):
            frames = channel.get_frames( nbframes )
            a = AudioFrames( frames, channel.get_sampwidth(), 1)
            self.volumes[i] = a.rms()

        if self.volumes[-1] == 0:
            self.volumes.pop()

        # Returns to the position where we was before
        channel.seek(pos)

        self.rms = channel.rms()
예제 #2
0
    def get_silence(self, p=0.250, v=150, s=0.):
        """
        Estimates silences from an audio file.
        @deprecated

        @param p (float) Minimum silence duration in seconds
        @param v (int) Expected minimum volume (rms value)
        @param s (float) Shift delta duration in seconds
        @return a set of frames corresponding to silences.

        """
        self.channel.seek(0)
        self.silence = []

        # Once silence has been found, continue searching in this interval
        nbreadframes = int(self.volstats.get_winlen() * self.channel.get_framerate())
        afterloop_frames = int(nbreadframes/2) #int((nbreadframes/2) * self.channel.get_framerate())
        initpos = i = self.channel.tell()

        # This scans the file in steps of frames whether a section's volume
        # is lower than silence_cap, if it is it is written to silence.
        while i < self.channel.get_nframes():

            curframe = self.channel.get_frames(nbreadframes)

            a = AudioFrames( curframe, self.channel.get_sampwidth(), 1)
            #volume = audioutils.get_rms(curframe, self.channel.get_sampwidth())
            volume = a.rms()
            if volume < v:

                # Continue searching in smaller steps whether the silence is
                # longer than read frames but smaller than read frames * 2.
                while volume < v and self.channel.tell() < self.channel.get_nframes():
                    curframe = self.channel.get_frames(afterloop_frames)

                    a = AudioFrames( curframe, self.channel.get_sampwidth(), 1)
                    volume = a.rms()
                    #volume   = audioutils.get_rms(curframe, self.channel.get_sampwidth())

                # If the last sequence of silence ends where the new one starts
                # it's a continuous range.
                if self.silence and self.silence[-1][1] == i:
                    self.silence[-1][1] = self.channel.tell()
                else:
                # append if silence is long enough
                    duree = self.channel.tell() - i
                    nbmin = int( (p+s) * self.channel.get_framerate())
                    if duree > nbmin:
                        # Adjust silence start-pos
                        __startpos = i + ( s * self.channel.get_framerate() )
                        # Adjust silence end-pos
                        __endpos = self.channel.tell() - ( s * self.channel.get_framerate() )
                        self.silence.append([__startpos, __endpos])

            i = self.channel.tell()

        # Return the position in the file to where it was when we got it.
        self.channel.seek(initpos)
예제 #3
0
    def remove_offset(self):
        """
        Convert the channel by removing the offset in the channel.

        """
        newchannel = Channel()
        newchannel.sampwidth = self.sampwidth
        newchannel.framerate = self.framerate
        a = AudioFrames( self.channel.get_frames(self.channel.get_nframes()), self.channel.get_sampwidth(), 1)
        avg = a.avg()
        newchannel.set_frames( a.bias( - avg ) )

        self.channel = newchannel
예제 #4
0
파일: audio.py 프로젝트: brigittebigi/sppas
    def rms(self):
        """
        Return the root mean square of the whole file

        @return the root mean square of the audio file

        """
        pos = self.tell()
        self.seek(0)
        a = AudioFrames(self.read_frames(self.get_nframes()), self.get_sampwidth(), self.get_nchannels())
        self.seek(pos)

        return a.rms()
예제 #5
0
파일: audio.py 프로젝트: brigittebigi/sppas
    def clipping_rate(self, factor):
        """
        Return the clipping rate of the frames

        @param factor (float) An interval to be more precise on clipping rate.
        It will consider that all frames outside the interval are clipped.
        Factor has to be between 0 and 1.

        """
        pos = self.tell()
        self.seek(0)
        a = AudioFrames(self.read_frames(self.get_nframes()), self.get_sampwidth())
        self.seek(pos)

        return a.clipping_rate(factor)
예제 #6
0
    def bias(self, biasvalue):
        """
        Convert the channel with a bias added to each frame.
        Samples wrap around in case of overflow.

        @param biasvalue (int) the value to bias the frames

        """
        if biasvalue == 0:
            return
        newchannel = Channel()
        newchannel.sampwidth = self.sampwidth
        newchannel.framerate = self.framerate
        a = AudioFrames( self.channel.get_frames(self.channel.get_nframes()), self.channel.get_sampwidth(), 1)
        newchannel.set_frames( a.bias( biasvalue ) )

        self.channel = newchannel
예제 #7
0
    def mul(self, factor):
        """
        Convert the channel that has all frames in the original channel are
        multiplied by the floating-point value factor.
        Samples are truncated in case of overflow.

        @param factor (float) the factor to multiply the frames

        """
        if factor == 1.:
            return
        newchannel = Channel()
        newchannel.sampwidth = self.sampwidth
        newchannel.framerate = self.framerate
        a = AudioFrames( self.channel.get_frames(self.channel.get_nframes()), self.channel.get_sampwidth(), 1)
        newchannel.set_frames( a.mul(factor) )

        self.channel = newchannel
예제 #8
0
    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()
예제 #9
0
    print "  - max:           ", audiovol.max()
    print "  - mean:          ", round(audiovol.mean(),2)
    print "  - median:        ", round(audiovol.median(),2)
    print "  - stdev:         ", round(audiovol.stdev(),2)
    print "  - coefvariation: ", round(audiovol.coefvariation(),2)

else:

    for n in range(nc):
        print "Channel %d:"%(n)
        cidx = audio.extract_channel(n)
        channel = audio.get_channel(cidx)

        # Values related to amplitude
        frames = channel.get_frames(channel.get_nframes())
        ca = AudioFrames(frames, channel.get_sampwidth(), 1)
        for i in range(2,9,2):
            f = float(i)/10.
            c = ca.clipping_rate( f ) * 100.
            print "  - factor=%.1f:      %.3f"%(f,c)

        # RMS (=volume)
        cv = ChannelVolume( channel )
        print "  Volume:"
        print "    - min:           ", cv.min()
        print "    - max:           ", cv.max()
        print "    - mean:          ", round(cv.mean(),2)
        print "    - median:        ", round(cv.median(),2)
        print "    - stdev:         ", round(cv.stdev(),2)
        print "    - coefvariation: ", round(cv.coefvariation(),2)
예제 #10
0
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