Changeset 633400d for python/lib


Ignore:
Timestamp:
Dec 5, 2018, 10:34:39 PM (6 years ago)
Author:
Paul Brossier <piem@piem.org>
Branches:
feature/cnn, feature/crepe, feature/pitchshift, feature/timestretch, fix/ffmpeg5, master
Children:
283a619a
Parents:
5b46bc3 (diff), f19db54 (diff)
Note: this is a merge changeset, the changes displayed below correspond to the merge itself.
Use the (diff) links above to see all the changes relative to each parent.
Message:

Merge branch 'master' into feature/pitchshift

Location:
python/lib
Files:
2 added
1 deleted
6 edited

Legend:

Unmodified
Added
Removed
  • python/lib/aubio/__init__.py

    r5b46bc3 r633400d  
    11#! /usr/bin/env python
     2# -*- coding: utf8 -*-
     3
     4"""
     5aubio
     6=====
     7
     8Provides a number of classes and functions for music and audio signal
     9analysis.
     10
     11How to use the documentation
     12----------------------------
     13
     14Documentation of the python module is available as docstrings provided
     15within the code, and a reference guide available online from `the
     16aubio homepage <https://aubio.org/documentation>`_.
     17
     18The docstrings examples are written assuming `aubio` and `numpy` have been
     19imported with:
     20
     21>>> import aubio
     22>>> import numpy as np
     23"""
    224
    325import numpy
     26from ._aubio import __version__ as version
     27from ._aubio import float_type
    428from ._aubio import *
    5 from ._aubio import float_type
    629from .midiconv import *
    730from .slicing import *
    831
     32
    933class fvec(numpy.ndarray):
    10     """a numpy vector holding audio samples"""
     34    """fvec(input_arg=1024)
     35    A vector holding float samples.
    1136
    12     def __new__(cls, input_arg=1024, **kwargs):
     37    If `input_arg` is an `int`, a 1-dimensional vector of length `input_arg`
     38    will be created and filled with zeros. Otherwise, if `input_arg` is an
     39    `array_like` object, it will be converted to a 1-dimensional vector of
     40    type :data:`float_type`.
     41
     42    Parameters
     43    ----------
     44    input_arg : `int` or `array_like`
     45        Can be a positive integer, or any object that can be converted to
     46        a numpy array with :func:`numpy.array`.
     47
     48    Examples
     49    --------
     50    >>> aubio.fvec(10)
     51    array([0., 0., 0., 0., 0., 0., 0., 0., 0., 0.], dtype=float32)
     52    >>> aubio.fvec([0,1,2])
     53    array([0., 1., 2.], dtype=float32)
     54    >>> a = np.arange(10); type(a), type(aubio.fvec(a))
     55    (<class 'numpy.ndarray'>, <class 'numpy.ndarray'>)
     56    >>> a.dtype, aubio.fvec(a).dtype
     57    (dtype('int64'), dtype('float32'))
     58
     59    Notes
     60    -----
     61
     62    In the Python world, `fvec` is simply a subclass of
     63    :class:`numpy.ndarray`. In practice, any 1-dimensional `numpy.ndarray` of
     64    `dtype` :data:`float_type` may be passed to methods accepting
     65    `fvec` as parameter. For instance, `sink()` or `pvoc()`.
     66
     67    See Also
     68    --------
     69    cvec : a container holding spectral data
     70    numpy.ndarray : parent class of :class:`fvec`
     71    numpy.zeros : create a numpy array filled with zeros
     72    numpy.array : create a numpy array from an existing object
     73    """
     74    def __new__(cls, input_arg=1024):
    1375        if isinstance(input_arg, int):
    1476            if input_arg == 0:
    1577                raise ValueError("vector length of 1 or more expected")
    16             return numpy.zeros(input_arg, dtype=float_type, **kwargs)
     78            return numpy.zeros(input_arg, dtype=float_type, order='C')
    1779        else:
    18             return numpy.array(input_arg, dtype=float_type, **kwargs)
     80            np_input = numpy.array(input_arg, dtype=float_type, order='C')
     81            if len(np_input.shape) != 1:
     82                raise ValueError("input_arg should have shape (n,)")
     83            if np_input.shape[0] == 0:
     84                raise ValueError("vector length of 1 or more expected")
     85            return np_input
  • python/lib/aubio/midiconv.py

    r5b46bc3 r633400d  
    22""" utilities to convert midi note number to and from note names """
    33
    4 __all__ = ['note2midi', 'midi2note', 'freq2note']
     4import sys
     5from ._aubio import freqtomidi, miditofreq
    56
    6 import sys
     7__all__ = ['note2midi', 'midi2note', 'freq2note', 'note2freq']
     8
    79py3 = sys.version_info[0] == 3
    810if py3:
     
    1315    int_instances = (int, long)
    1416
     17
    1518def note2midi(note):
    16     " convert note name to midi note number, e.g. [C-1, G9] -> [0, 127] "
    17     _valid_notenames = {'C': 0, 'D': 2, 'E': 4, 'F': 5, 'G': 7, 'A': 9, 'B': 11}
    18     _valid_modifiers = {None: 0, u'♮': 0, '#': +1, u'♯': +1, u'\udd2a': +2,
    19                         'b': -1, u'♭': -1, u'\ufffd': -2}
     19    """Convert note name to midi note number.
     20
     21    Input string `note` should be composed of one note root
     22    and one octave, with optionally one modifier in between.
     23
     24    List of valid components:
     25
     26    - note roots: `C`, `D`, `E`, `F`, `G`, `A`, `B`,
     27    - modifiers: `b`, `#`, as well as unicode characters
     28      `𝄫`, `♭`, `♮`, `♯` and `𝄪`,
     29    - octave numbers: `-1` -> `11`.
     30
     31    Parameters
     32    ----------
     33    note : str
     34        note name
     35
     36    Returns
     37    -------
     38    int
     39        corresponding midi note number
     40
     41    Examples
     42    --------
     43    >>> aubio.note2midi('C#4')
     44    61
     45    >>> aubio.note2midi('B♭5')
     46    82
     47
     48    Raises
     49    ------
     50    TypeError
     51        If `note` was not a string.
     52    ValueError
     53        If an error was found while converting `note`.
     54
     55    See Also
     56    --------
     57    midi2note, freqtomidi, miditofreq
     58    """
     59    _valid_notenames = {'C': 0, 'D': 2, 'E': 4, 'F': 5, 'G': 7,
     60                        'A': 9, 'B': 11}
     61    _valid_modifiers = {
     62            u'𝄫': -2,                         # double flat
     63            u'♭': -1, 'b': -1, '\u266d': -1,  # simple flat
     64            u'♮': 0, '\u266e': 0, None: 0,    # natural
     65            '#': +1, u'♯': +1, '\u266f': +1,  # sharp
     66            u'𝄪': +2,                         # double sharp
     67            }
    2068    _valid_octaves = range(-1, 10)
    2169    if not isinstance(note, str_instances):
    22         raise TypeError("a string is required, got %s (%s)" % (note, str(type(note))))
     70        msg = "a string is required, got {:s} ({:s})"
     71        raise TypeError(msg.format(str(type(note)), repr(note)))
    2372    if len(note) not in range(2, 5):
    24         raise ValueError("string of 2 to 4 characters expected, got %d (%s)" \
    25                          % (len(note), note))
    26     notename, modifier, octave = [None]*3
     73        msg = "string of 2 to 4 characters expected, got {:d} ({:s})"
     74        raise ValueError(msg.format(len(note), note))
     75    notename, modifier, octave = [None] * 3
    2776
    2877    if len(note) == 4:
     
    4796        raise ValueError("%s is not a valid octave" % octave)
    4897
    49     midi = 12 + octave * 12 + _valid_notenames[notename] + _valid_modifiers[modifier]
     98    midi = (octave + 1) * 12 + _valid_notenames[notename] \
     99                             + _valid_modifiers[modifier]
    50100    if midi > 127:
    51101        raise ValueError("%s is outside of the range C-2 to G8" % note)
    52102    return midi
    53103
     104
    54105def midi2note(midi):
    55     " convert midi note number to note name, e.g. [0, 127] -> [C-1, G9] "
     106    """Convert midi note number to note name.
     107
     108    Parameters
     109    ----------
     110    midi : int [0, 128]
     111        input midi note number
     112
     113    Returns
     114    -------
     115    str
     116        note name
     117
     118    Examples
     119    --------
     120    >>> aubio.midi2note(70)
     121    'A#4'
     122    >>> aubio.midi2note(59)
     123    'B3'
     124
     125    Raises
     126    ------
     127    TypeError
     128        If `midi` was not an integer.
     129    ValueError
     130        If `midi` is out of the range `[0, 128]`.
     131
     132    See Also
     133    --------
     134    note2midi, miditofreq, freqtomidi
     135    """
    56136    if not isinstance(midi, int_instances):
    57137        raise TypeError("an integer is required, got %s" % midi)
    58138    if midi not in range(0, 128):
    59         raise ValueError("an integer between 0 and 127 is excepted, got %d" % midi)
    60     _valid_notenames = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B']
     139        msg = "an integer between 0 and 127 is excepted, got {:d}"
     140        raise ValueError(msg.format(midi))
     141    _valid_notenames = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#',
     142                        'A', 'A#', 'B']
    61143    return _valid_notenames[midi % 12] + str(int(midi / 12) - 1)
    62144
     145
    63146def freq2note(freq):
    64     " convert frequency in Hz to nearest note name, e.g. [0, 22050.] -> [C-1, G9] "
    65     from aubio import freqtomidi
    66     return midi2note(int(freqtomidi(freq)))
     147    """Convert frequency in Hz to nearest note name.
     148
     149    Parameters
     150    ----------
     151    freq : float [0, 23000[
     152        input frequency, in Hz
     153
     154    Returns
     155    -------
     156    str
     157        name of the nearest note
     158
     159    Example
     160    -------
     161    >>> aubio.freq2note(440)
     162    'A4'
     163    >>> aubio.freq2note(220.1)
     164    'A3'
     165    """
     166    nearest_note = int(freqtomidi(freq) + .5)
     167    return midi2note(nearest_note)
     168
     169
     170def note2freq(note):
     171    """Convert note name to corresponding frequency, in Hz.
     172
     173    Parameters
     174    ----------
     175    note : str
     176        input note name
     177
     178    Returns
     179    -------
     180    freq : float [0, 23000[
     181        frequency, in Hz
     182
     183    Example
     184    -------
     185    >>> aubio.note2freq('A4')
     186    440
     187    >>> aubio.note2freq('A3')
     188    220.1
     189    """
     190    midi = note2midi(note)
     191    return miditofreq(midi)
  • python/lib/aubio/slicing.py

    r5b46bc3 r633400d  
    66_max_timestamp = 1e120
    77
     8
    89def slice_source_at_stamps(source_file, timestamps, timestamps_end=None,
    9                            output_dir=None, samplerate=0, hopsize=256):
    10     """ slice a sound file at given timestamps """
     10                           output_dir=None, samplerate=0, hopsize=256,
     11                           create_first=False):
     12    """Slice a sound file at given timestamps.
    1113
    12     if timestamps is None or len(timestamps) == 0:
     14    This function reads `source_file` and creates slices, new smaller
     15    files each starting at `t` in `timestamps`, a list of integer
     16    corresponding to time locations in `source_file`, in samples.
     17
     18    If `timestamps_end` is unspecified, the slices will end at
     19    `timestamps_end[n] = timestamps[n+1]-1`, or the end of file.
     20    Otherwise, `timestamps_end` should be a list with the same length
     21    as `timestamps` containing the locations of the end of each slice.
     22
     23    If `output_dir` is unspecified, the new slices will be written in
     24    the current directory. If `output_dir` is a string, new slices
     25    will be written in `output_dir`, after creating the directory if
     26    required.
     27
     28    The default `samplerate` is 0, meaning the original sampling rate
     29    of `source_file` will be used. When using a sampling rate
     30    different to the one of the original files, `timestamps` and
     31    `timestamps_end` should be expressed in the re-sampled signal.
     32
     33    The `hopsize` parameter simply tells :class:`source` to use this
     34    hopsize and does not change the output slices.
     35
     36    If `create_first` is True and `timestamps` does not start with `0`, the
     37    first slice from `0` to `timestamps[0] - 1` will be automatically added.
     38
     39    Parameters
     40    ----------
     41    source_file : str
     42        path of the resource to slice
     43    timestamps : :obj:`list` of :obj:`int`
     44        time stamps at which to slice, in samples
     45    timestamps_end : :obj:`list` of :obj:`int` (optional)
     46        time stamps at which to end the slices
     47    output_dir : str (optional)
     48        output directory to write the slices to
     49    samplerate : int (optional)
     50        samplerate to read the file at
     51    hopsize : int (optional)
     52        number of samples read from source per iteration
     53    create_first : bool (optional)
     54        always create the slice at the start of the file
     55
     56    Examples
     57    --------
     58    Create two slices: the first slice starts at the beginning of the
     59    input file `loop.wav` and lasts exactly one second, starting at
     60    sample `0` and ending at sample `44099`; the second slice starts
     61    at sample `44100` and lasts until the end of the input file:
     62
     63    >>> aubio.slice_source_at_stamps('loop.wav', [0, 44100])
     64
     65    Create one slice, from 1 second to 2 seconds:
     66
     67    >>> aubio.slice_source_at_stamps('loop.wav', [44100], [44100 * 2 - 1])
     68
     69    Notes
     70    -----
     71    Slices may be overlapping. If `timestamps_end` is `1` element
     72    shorter than `timestamps`, the last slice will end at the end of
     73    the file.
     74    """
     75
     76    if not timestamps:
    1377        raise ValueError("no timestamps given")
    1478
    15     if timestamps[0] != 0:
     79    if timestamps[0] != 0 and create_first:
    1680        timestamps = [0] + timestamps
    1781        if timestamps_end is not None:
     
    1983
    2084    if timestamps_end is not None:
    21         if len(timestamps_end) != len(timestamps):
     85        if len(timestamps_end) == len(timestamps) - 1:
     86            timestamps_end = timestamps_end + [_max_timestamp]
     87        elif len(timestamps_end) != len(timestamps):
    2288            raise ValueError("len(timestamps_end) != len(timestamps)")
    2389    else:
     
    2591
    2692    regions = list(zip(timestamps, timestamps_end))
    27     #print regions
    2893
    2994    source_base_name, _ = os.path.splitext(os.path.basename(source_file))
     
    3398        source_base_name = os.path.join(output_dir, source_base_name)
    3499
    35     def new_sink_name(source_base_name, timestamp, samplerate):
    36         """ create a sink based on a timestamp in samples, converted in seconds """
     100    def _new_sink_name(source_base_name, timestamp, samplerate):
     101        # create name based on a timestamp in samples, converted in seconds
    37102        timestamp_seconds = timestamp / float(samplerate)
    38103        return source_base_name + "_%011.6f" % timestamp_seconds + '.wav'
     
    49114        vec, read = _source.do_multi()
    50115        # if the total number of frames read will exceed the next region start
    51         if len(regions) and total_frames + read >= regions[0][0]:
    52             #print "getting", regions[0], "at", total_frames
     116        while regions and total_frames + read >= regions[0][0]:
    53117            # get next region
    54118            start_stamp, end_stamp = regions.pop(0)
    55119            # create a name for the sink
    56             new_sink_path = new_sink_name(source_base_name, start_stamp, samplerate)
     120            new_sink_path = _new_sink_name(source_base_name, start_stamp,
     121                                           samplerate)
    57122            # create its sink
    58123            _sink = sink(new_sink_path, samplerate, _source.channels)
    59124            # create a dictionary containing all this
    60             new_slice = {'start_stamp': start_stamp, 'end_stamp': end_stamp, 'sink': _sink}
     125            new_slice = {'start_stamp': start_stamp, 'end_stamp': end_stamp,
     126                         'sink': _sink}
    61127            # append the dictionary to the current list of slices
    62128            slices.append(new_slice)
     
    70136            # number of samples yet to written be until end of region
    71137            remaining = end_stamp - total_frames + 1
    72             #print current_slice, remaining, start
    73138            # not enough frames remaining, time to split
    74139            if remaining < read:
     
    76141                    # write remaining samples from current region
    77142                    _sink.do_multi(vec[:, start:remaining], remaining - start)
    78                     #print "closing region", "remaining", remaining
    79143                    # close this file
    80144                    _sink.close()
     
    83147                _sink.do_multi(vec[:, start:read], read - start)
    84148        total_frames += read
     149        # remove old slices
     150        slices = list(filter(lambda s: s['end_stamp'] > total_frames,
     151                             slices))
    85152        if read < hopsize:
    86153            break
  • python/lib/gen_code.py

    r5b46bc3 r633400d  
    33    'buf_size': 'Py_default_vector_length',
    44    'win_s': 'Py_default_vector_length',
     5    'size': 'Py_default_vector_length',
    56    # and here too
    67    'hop_size': 'Py_default_vector_length / 2',
     
    8586        'tss': 'self->buf_size',
    8687        'pitchshift': 'self->hop_size',
     88        'dct': 'self->size',
    8789        }
    8890
     
    180182        self.do_outputs = get_params_types_names(self.do_proto)[2:]
    181183        struct_output_str = ["PyObject *{0[name]}; {1} c_{0[name]}".format(i, i['type'][:-1]) for i in self.do_outputs]
     184        if len(self.prototypes['rdo']):
     185            rdo_outputs = get_params_types_names(prototypes['rdo'][0])[2:]
     186            struct_output_str += ["PyObject *{0[name]}; {1} c_{0[name]}".format(i, i['type'][:-1]) for i in rdo_outputs]
     187            self.outputs += rdo_outputs
    182188        self.struct_outputs = ";\n    ".join(struct_output_str)
    183189
     
    187193    def gen_code(self):
    188194        out = ""
    189         out += self.gen_struct()
    190         out += self.gen_doc()
    191         out += self.gen_new()
    192         out += self.gen_init()
    193         out += self.gen_del()
    194         out += self.gen_do()
    195         out += self.gen_memberdef()
    196         out += self.gen_set()
    197         out += self.gen_get()
    198         out += self.gen_methodef()
    199         out += self.gen_typeobject()
     195        try:
     196            out += self.gen_struct()
     197            out += self.gen_doc()
     198            out += self.gen_new()
     199            out += self.gen_init()
     200            out += self.gen_del()
     201            out += self.gen_do()
     202            if len(self.prototypes['rdo']):
     203                self.do_proto = self.prototypes['rdo'][0]
     204                self.do_inputs = [get_params_types_names(self.do_proto)[1]]
     205                self.do_outputs = get_params_types_names(self.do_proto)[2:]
     206                out += self.gen_do(method='rdo')
     207            out += self.gen_memberdef()
     208            out += self.gen_set()
     209            out += self.gen_get()
     210            out += self.gen_methodef()
     211            out += self.gen_typeobject()
     212        except Exception as e:
     213            print ("Failed generating code for", self.shortname)
     214            raise
    200215        return out
    201216
     
    381396        return out
    382397
    383     def gen_do(self):
     398    def gen_do(self, method = 'do'):
    384399        out = """
    385400// do {shortname}
    386401static PyObject*
    387 Py_{shortname}_do  (Py_{shortname} * self, PyObject * args)
    388 {{""".format(**self.__dict__)
     402Pyaubio_{shortname}_{method}  (Py_{shortname} * self, PyObject * args)
     403{{""".format(method = method, **self.__dict__)
    389404        input_params = self.do_inputs
    390405        output_params = self.do_outputs
     
    462477""".format(**self.__dict__)
    463478        for set_param in self.prototypes['set']:
    464             params = get_params_types_names(set_param)[1]
    465             paramtype = params['type']
     479            params = get_params_types_names(set_param)[1:]
     480            param = self.shortname.split('_set_')[-1]
     481            paramdecls = "".join(["""
     482   {0} {1};""".format(p['type'], p['name']) for p in params])
    466483            method_name = get_name(set_param)
    467484            param = method_name.split('aubio_'+self.shortname+'_set_')[-1]
    468             pyparamtype = pyargparse_chars[paramtype]
     485            refs = ", ".join(["&%s" % p['name'] for p in params])
     486            paramlist = ", ".join(["%s" % p['name'] for p in params])
     487            if len(params):
     488                paramlist = "," + paramlist
     489            pyparamtypes = ''.join([pyargparse_chars[p['type']] for p in params])
    469490            out += """
    470491static PyObject *
     
    472493{{
    473494  uint_t err = 0;
    474   {paramtype} {param};
    475 
    476   if (!PyArg_ParseTuple (args, "{pyparamtype}", &{param})) {{
     495  {paramdecls}
     496""".format(param = param, paramdecls = paramdecls, **self.__dict__)
     497
     498            if len(refs) and len(pyparamtypes):
     499                out += """
     500
     501  if (!PyArg_ParseTuple (args, "{pyparamtypes}", {refs})) {{
    477502    return NULL;
    478503  }}
    479   err = aubio_{shortname}_set_{param} (self->o, {param});
     504""".format(pyparamtypes = pyparamtypes, refs = refs)
     505
     506            out += """
     507  err = aubio_{shortname}_set_{param} (self->o {paramlist});
    480508
    481509  if (err > 0) {{
    482     PyErr_SetString (PyExc_ValueError, "error running aubio_{shortname}_set_{param}");
     510    if (PyErr_Occurred() == NULL) {{
     511      PyErr_SetString (PyExc_ValueError, "error running aubio_{shortname}_set_{param}");
     512    }} else {{
     513      // change the RuntimeError into ValueError
     514      PyObject *type, *value, *traceback;
     515      PyErr_Fetch(&type, &value, &traceback);
     516      PyErr_Restore(PyExc_ValueError, value, traceback);
     517    }}
    483518    return NULL;
    484519  }}
    485520  Py_RETURN_NONE;
    486521}}
    487 """.format(param = param, paramtype = paramtype, pyparamtype = pyparamtype, **self.__dict__)
     522""".format(param = param, refs = refs, paramdecls = paramdecls,
     523        pyparamtypes = pyparamtypes, paramlist = paramlist, **self.__dict__)
    488524        return out
    489525
     
    526562  {{"{shortname}", (PyCFunction) Py{name},
    527563    METH_NOARGS, ""}},""".format(name = name, shortname = shortname)
     564        for m in self.prototypes['rdo']:
     565            name = get_name(m)
     566            shortname = name.replace('aubio_%s_' % self.shortname, '')
     567            out += """
     568  {{"{shortname}", (PyCFunction) Py{name},
     569    METH_VARARGS, ""}},""".format(name = name, shortname = shortname)
    528570        out += """
    529571  {NULL} /* sentinel */
     
    551593  0,
    552594  0,
    553   (ternaryfunc)Py_{shortname}_do,
     595  (ternaryfunc)Pyaubio_{shortname}_do,
    554596  0,
    555597  0,
  • python/lib/gen_external.py

    r5b46bc3 r633400d  
    11import distutils.ccompiler
    2 import sys, os, subprocess, glob
     2import sys
     3import os
     4import subprocess
     5import glob
     6from distutils.sysconfig import customize_compiler
     7from gen_code import MappedObject
    38
    49header = os.path.join('src', 'aubio.h')
     
    914"""
    1015
    11 skip_objects = [
    12   # already in ext/
    13   'fft',
    14   'pvoc',
    15   'filter',
    16   'filterbank',
    17   #'resampler',
    18   # AUBIO_UNSTABLE
    19   'hist',
    20   'parameter',
    21   'scale',
    22   'beattracking',
    23   'resampler',
    24   'peakpicker',
    25   'pitchfcomb',
    26   'pitchmcomb',
    27   'pitchschmitt',
    28   'pitchspecacf',
    29   'pitchyin',
    30   'pitchyinfft',
    31   'sink',
    32   'sink_apple_audio',
    33   'sink_sndfile',
    34   'sink_wavwrite',
    35   #'mfcc',
    36   'source',
    37   'source_apple_audio',
    38   'source_sndfile',
    39   'source_avcodec',
    40   'source_wavread',
    41   #'sampler',
    42   'audio_unit',
    43   ]
     16default_skip_objects = [
     17    # already in ext/
     18    'fft',
     19    'pvoc',
     20    'filter',
     21    'filterbank',
     22    # AUBIO_UNSTABLE
     23    'hist',
     24    'parameter',
     25    'scale',
     26    'beattracking',
     27    'resampler',
     28    'peakpicker',
     29    'pitchfcomb',
     30    'pitchmcomb',
     31    'pitchschmitt',
     32    'pitchspecacf',
     33    'pitchyin',
     34    'pitchyinfft',
     35    'pitchyinfast',
     36    'sink',
     37    'sink_apple_audio',
     38    'sink_sndfile',
     39    'sink_wavwrite',
     40    #'mfcc',
     41    'source',
     42    'source_apple_audio',
     43    'source_sndfile',
     44    'source_avcodec',
     45    'source_wavread',
     46    #'sampler',
     47    'audio_unit',
     48    'spectral_whitening',
     49]
     50
    4451
    4552def get_preprocessor():
    4653    # findout which compiler to use
    47     from distutils.sysconfig import customize_compiler
    4854    compiler_name = distutils.ccompiler.get_default_compiler()
    4955    compiler = distutils.ccompiler.new_compiler(compiler=compiler_name)
     
    6066
    6167    cpp_cmd = None
    62     if hasattr(compiler, 'preprocessor'): # for unixccompiler
     68    if hasattr(compiler, 'preprocessor'):  # for unixccompiler
    6369        cpp_cmd = compiler.preprocessor
    64     elif hasattr(compiler, 'compiler'): # for ccompiler
     70    elif hasattr(compiler, 'compiler'):  # for ccompiler
    6571        cpp_cmd = compiler.compiler.split()
    6672        cpp_cmd += ['-E']
    67     elif hasattr(compiler, 'cc'): # for msvccompiler
     73    elif hasattr(compiler, 'cc'):  # for msvccompiler
    6874        cpp_cmd = compiler.cc.split()
    6975        cpp_cmd += ['-E']
     76
     77    # On win-amd64 (py3.x), the default compiler is cross-compiling, from x86
     78    # to amd64 with %WIN_SDK_ROOT%\x86_amd64\cl.exe, but using this binary as a
     79    # pre-processor generates no output, so we use %WIN_SDK_ROOT%\cl.exe
     80    # instead.
     81    if len(cpp_cmd) > 1 and 'cl.exe' in cpp_cmd[-2]:
     82        plat = os.path.basename(os.path.dirname(cpp_cmd[-2]))
     83        if plat == 'x86_amd64':
     84            print('workaround on win64 to avoid empty pre-processor output')
     85            cpp_cmd[-2] = cpp_cmd[-2].replace('x86_amd64', '')
     86        elif True in ['amd64' in f for f in cpp_cmd]:
     87            print('warning: not using workaround for', cpp_cmd[0], plat)
    7088
    7189    if not cpp_cmd:
     
    7391        cpp_cmd = os.environ.get('CC', 'cc').split()
    7492        cpp_cmd += ['-E']
    75 
     93    if 'emcc' in cpp_cmd:
     94        cpp_cmd += ['-x', 'c'] # emcc defaults to c++, force C language
    7695    return cpp_cmd
    7796
    78 def get_cpp_objects(header=header):
     97
     98def get_c_declarations(header=header, usedouble=False):
     99    ''' return a dense and preprocessed  string of all c declarations implied by aubio.h
     100    '''
     101    cpp_output = get_cpp_output(header=header, usedouble=usedouble)
     102    return filter_cpp_output (cpp_output)
     103
     104
     105def get_cpp_output(header=header, usedouble=False):
     106    ''' find and run a C pre-processor on aubio.h '''
    79107    cpp_cmd = get_preprocessor()
    80108
    81109    macros = [('AUBIO_UNSTABLE', 1)]
     110    if usedouble:
     111        macros += [('HAVE_AUBIO_DOUBLE', 1)]
    82112
    83113    if not os.path.isfile(header):
     
    90120    print("Running command: {:s}".format(" ".join(cpp_cmd)))
    91121    proc = subprocess.Popen(cpp_cmd,
    92             stderr=subprocess.PIPE,
    93             stdout=subprocess.PIPE)
     122                            stderr=subprocess.PIPE,
     123                            stdout=subprocess.PIPE)
    94124    assert proc, 'Proc was none'
    95125    cpp_output = proc.stdout.read()
    96126    err_output = proc.stderr.read()
     127    if err_output:
     128        print("Warning: preprocessor produced errors or warnings:\n%s" \
     129                % err_output.decode('utf8'))
    97130    if not cpp_output:
    98         raise Exception("preprocessor output is empty:\n%s" % err_output)
    99     elif err_output:
    100         print ("Warning: preprocessor produced warnings:\n%s" % err_output)
     131        raise_msg = "preprocessor output is empty! Running command " \
     132                + "\"%s\" failed" % " ".join(cpp_cmd)
     133        if err_output:
     134            raise_msg += " with stderr: \"%s\"" % err_output.decode('utf8')
     135        else:
     136            raise_msg += " with no stdout or stderr"
     137        raise Exception(raise_msg)
    101138    if not isinstance(cpp_output, list):
    102139        cpp_output = [l.strip() for l in cpp_output.decode('utf8').split('\n')]
    103140
    104     cpp_output = filter(lambda y: len(y) > 1, cpp_output)
     141    return cpp_output
     142
     143def filter_cpp_output(cpp_raw_output):
     144    ''' prepare cpp-output for parsing '''
     145    cpp_output = filter(lambda y: len(y) > 1, cpp_raw_output)
    105146    cpp_output = list(filter(lambda y: not y.startswith('#'), cpp_output))
    106147
    107148    i = 1
    108149    while 1:
    109         if i >= len(cpp_output): break
    110         if cpp_output[i-1].endswith(',') or cpp_output[i-1].endswith('{') or cpp_output[i].startswith('}'):
    111             cpp_output[i] = cpp_output[i-1] + ' ' + cpp_output[i]
    112             cpp_output.pop(i-1)
     150        if i >= len(cpp_output):
     151            break
     152        if ('{' in cpp_output[i - 1]) and ('}' not in cpp_output[i - 1]) or (';' not in cpp_output[i - 1]):
     153            cpp_output[i] = cpp_output[i - 1] + ' ' + cpp_output[i]
     154            cpp_output.pop(i - 1)
     155        elif ('}' in cpp_output[i]):
     156            cpp_output[i] = cpp_output[i - 1] + ' ' + cpp_output[i]
     157            cpp_output.pop(i - 1)
    113158        else:
    114159            i += 1
    115160
    116     typedefs = filter(lambda y: y.startswith ('typedef struct _aubio'), cpp_output)
    117 
     161    # clean pointer notations
     162    tmp = []
     163    for l in cpp_output:
     164        tmp += [l.replace(' *', ' * ')]
     165    cpp_output = tmp
     166
     167    return cpp_output
     168
     169
     170def get_cpp_objects_from_c_declarations(c_declarations, skip_objects=None):
     171    if skip_objects is None:
     172        skip_objects = default_skip_objects
     173    typedefs = filter(lambda y: y.startswith('typedef struct _aubio'), c_declarations)
    118174    cpp_objects = [a.split()[3][:-1] for a in typedefs]
    119 
    120     return cpp_output, cpp_objects
    121 
    122 
    123 def analyze_cpp_output(cpp_objects, cpp_output):
     175    cpp_objects_filtered = filter(lambda y: not y[6:-2] in skip_objects, cpp_objects)
     176    return cpp_objects_filtered
     177
     178
     179def get_all_func_names_from_lib(lib):
     180    ''' return flat string of all function used in lib
     181    '''
     182    res = []
     183    for _, v in lib.items():
     184        if isinstance(v, dict):
     185            res += get_all_func_names_from_lib(v)
     186        elif isinstance(v, list):
     187            for elem in v:
     188                e = elem.split('(')
     189                if len(e) < 2:
     190                    continue  # not a function
     191                fname_part = e[0].strip().split(' ')
     192                fname = fname_part[-1]
     193                if fname:
     194                    res += [fname]
     195                else:
     196                    raise NameError('gen_lib : weird function: ' + str(e))
     197
     198    return res
     199
     200
     201def generate_lib_from_c_declarations(cpp_objects, c_declarations):
     202    ''' returns a lib from given cpp_object names
     203
     204    a lib is a dict grouping functions by family (onset,pitch...)
     205        each eement is itself a dict of functions grouped by puposes as :
     206        struct, new, del, do, get, set and other
     207    '''
    124208    lib = {}
    125209
    126210    for o in cpp_objects:
    127         if o[:6] != 'aubio_':
    128             continue
    129         shortname = o[6:-2]
    130         if shortname in skip_objects:
    131             continue
    132         lib[shortname] = {'struct': [], 'new': [], 'del': [], 'do': [], 'get': [], 'set': [], 'other': []}
     211        shortname = o
     212        if o[:6] == 'aubio_':
     213            shortname = o[6:-2]  # without aubio_ prefix and _t suffix
     214
     215        lib[shortname] = {'struct': [], 'new': [], 'del': [], 'do': [], 'rdo': [], 'get': [], 'set': [], 'other': []}
    133216        lib[shortname]['longname'] = o
    134217        lib[shortname]['shortname'] = shortname
    135         for fn in cpp_output:
    136             if o[:-1] in fn:
    137                 #print "found", o[:-1], "in", fn
     218
     219        fullshortname = o[:-2]  # name without _t suffix
     220
     221        for fn in c_declarations:
     222            func_name = fn.split('(')[0].strip().split(' ')[-1]
     223            if func_name.startswith(fullshortname + '_') or func_name.endswith(fullshortname):
     224                # print "found", shortname, "in", fn
    138225                if 'typedef struct ' in fn:
    139226                    lib[shortname]['struct'].append(fn)
    140227                elif '_do' in fn:
    141228                    lib[shortname]['do'].append(fn)
     229                elif '_rdo' in fn:
     230                    lib[shortname]['rdo'].append(fn)
    142231                elif 'new_' in fn:
    143232                    lib[shortname]['new'].append(fn)
     
    149238                    lib[shortname]['set'].append(fn)
    150239                else:
    151                     #print "no idea what to do about", fn
     240                    # print "no idea what to do about", fn
    152241                    lib[shortname]['other'].append(fn)
    153242    return lib
    154243
    155 def print_cpp_output_results(lib, cpp_output):
    156     for fn in cpp_output:
     244
     245def print_c_declarations_results(lib, c_declarations):
     246    for fn in c_declarations:
    157247        found = 0
    158248        for o in lib:
     
    161251                    found = 1
    162252        if found == 0:
    163             print ("missing", fn)
     253            print("missing", fn)
    164254
    165255    for o in lib:
    166256        for family in lib[o]:
    167257            if type(lib[o][family]) == str:
    168                 print ( "{:15s} {:10s} {:s}".format(o, family, lib[o][family] ) )
     258                print("{:15s} {:10s} {:s}".format(o, family, lib[o][family]))
    169259            elif len(lib[o][family]) == 1:
    170                 print ( "{:15s} {:10s} {:s}".format(o, family, lib[o][family][0] ) )
     260                print("{:15s} {:10s} {:s}".format(o, family, lib[o][family][0]))
    171261            else:
    172                 print ( "{:15s} {:10s} {:s}".format(o, family, lib[o][family] ) )
     262                print("{:15s} {:10s} {:s}".format(o, family, lib[o][family]))
    173263
    174264
    175265def generate_external(header=header, output_path=output_path, usedouble=False, overwrite=True):
    176     if not os.path.isdir(output_path): os.mkdir(output_path)
    177     elif not overwrite: return glob.glob(os.path.join(output_path, '*.c'))
    178 
    179     cpp_output, cpp_objects = get_cpp_objects(header)
    180 
    181     lib = analyze_cpp_output(cpp_objects, cpp_output)
    182     # print_cpp_output_results(lib, cpp_output)
     266    if not os.path.isdir(output_path):
     267        os.mkdir(output_path)
     268    elif not overwrite:
     269        return sorted(glob.glob(os.path.join(output_path, '*.c')))
     270
     271    c_declarations = get_c_declarations(header, usedouble=usedouble)
     272    cpp_objects = get_cpp_objects_from_c_declarations(c_declarations)
     273
     274    lib = generate_lib_from_c_declarations(cpp_objects, c_declarations)
     275    # print_c_declarations_results(lib, c_declarations)
    183276
    184277    sources_list = []
    185     try:
    186         from .gen_code import MappedObject
    187     except (SystemError, ValueError):
    188         from gen_code import MappedObject
    189278    for o in lib:
    190279        out = source_header
    191         mapped = MappedObject(lib[o], usedouble = usedouble)
     280        mapped = MappedObject(lib[o], usedouble=usedouble)
    192281        out += mapped.gen_code()
    193282        output_file = os.path.join(output_path, 'gen-%s.c' % o)
    194283        with open(output_file, 'w') as f:
    195284            f.write(out)
    196             print ("wrote %s" % output_file )
     285            print("wrote %s" % output_file)
    197286            sources_list.append(output_file)
    198287
     
    206295  return ({pycheck_types});
    207296}}
    208 """.format(pycheck_types = check_types)
     297""".format(pycheck_types=check_types)
    209298
    210299    add_types = "".join(["""
    211300  Py_INCREF (&Py_{name}Type);
    212   PyModule_AddObject(m, "{name}", (PyObject *) & Py_{name}Type);""".format(name = o) for o in lib])
     301  PyModule_AddObject(m, "{name}", (PyObject *) & Py_{name}Type);""".format(name=o) for o in lib])
    213302    out += """
    214303
     
    217306{add_types}
    218307}}
    219 """.format(add_types = add_types)
     308""".format(add_types=add_types)
    220309
    221310    output_file = os.path.join(output_path, 'aubio-generated.c')
    222311    with open(output_file, 'w') as f:
    223312        f.write(out)
    224         print ("wrote %s" % output_file )
     313        print("wrote %s" % output_file)
    225314        sources_list.append(output_file)
    226315
     
    240329int generated_objects ( void );
    241330void add_generated_objects( PyObject *m );
    242 """.format(objlist = objlist)
     331""".format(objlist=objlist)
    243332
    244333    output_file = os.path.join(output_path, 'aubio-generated.h')
    245334    with open(output_file, 'w') as f:
    246335        f.write(out)
    247         print ("wrote %s" % output_file )
     336        print("wrote %s" % output_file)
    248337        # no need to add header to list of sources
    249338
    250     return sources_list
     339    return sorted(sources_list)
    251340
    252341if __name__ == '__main__':
    253     if len(sys.argv) > 1: header = sys.argv[1]
    254     if len(sys.argv) > 2: output_path = sys.argv[2]
     342    if len(sys.argv) > 1:
     343        header = sys.argv[1]
     344    if len(sys.argv) > 2:
     345        output_path = sys.argv[2]
    255346    generate_external(header, output_path)
  • python/lib/moresetuptools.py

    r5b46bc3 r633400d  
    33import sys, os, glob, subprocess
    44import distutils, distutils.command.clean, distutils.dir_util
    5 from .gen_external import generate_external, header, output_path
     5from gen_external import generate_external, header, output_path
     6
     7from this_version import get_aubio_version
    68
    79# inspired from https://gist.github.com/abergmeier/9488990
     
    2224
    2325    for package in packages:
     26        print("checking for {:s}".format(package))
    2427        cmd = ['pkg-config', '--libs', '--cflags', package]
    2528        try:
     
    5558def add_local_aubio_sources(ext):
    5659    """ build aubio inside python module instead of linking against libaubio """
    57     print("Warning: libaubio was not built with waf, adding src/")
    58     # create an empty header, macros will be passed on the command line
    59     fake_config_header = os.path.join('python', 'ext', 'config.h')
    60     distutils.file_util.write_file(fake_config_header, "")
    61     aubio_sources = glob.glob(os.path.join('src', '**.c'))
    62     aubio_sources += glob.glob(os.path.join('src', '*', '**.c'))
     60    print("Info: libaubio was not installed or built locally with waf, adding src/")
     61    aubio_sources = sorted(glob.glob(os.path.join('src', '**.c')))
     62    aubio_sources += sorted(glob.glob(os.path.join('src', '*', '**.c')))
    6363    ext.sources += aubio_sources
     64
     65def add_local_macros(ext, usedouble = False):
     66    if usedouble:
     67        ext.define_macros += [('HAVE_AUBIO_DOUBLE', 1)]
    6468    # define macros (waf puts them in build/src/config.h)
    6569    for define_macro in ['HAVE_STDLIB_H', 'HAVE_STDIO_H',
     
    7074        ext.define_macros += [(define_macro, 1)]
    7175
     76def add_external_deps(ext, usedouble = False):
    7277    # loof for additional packages
    7378    print("Info: looking for *optional* additional packages")
    74     packages = ['libavcodec', 'libavformat', 'libavutil', 'libavresample',
     79    packages = ['libavcodec', 'libavformat', 'libavutil',
     80                'libswresample', 'libavresample',
    7581                'jack',
    76                 'sndfile', 'samplerate',
     82                'sndfile',
    7783                'rubberband',
    7884                #'fftw3f',
    7985               ]
     86    # samplerate only works with float
     87    if usedouble is False:
     88        packages += ['samplerate']
     89    else:
     90        print("Info: not adding libsamplerate in double precision mode")
    8091    add_packages(packages, ext=ext)
    8192    if 'avcodec' in ext.libraries \
    8293            and 'avformat' in ext.libraries \
    83             and 'avutil' in ext.libraries \
    84             and 'avresample' in ext.libraries:
    85         ext.define_macros += [('HAVE_LIBAV', 1)]
    86     if 'jack' in ext.libraries:
    87         ext.define_macros += [('HAVE_JACK', 1)]
     94            and 'avutil' in ext.libraries:
     95        if 'swresample' in ext.libraries:
     96            ext.define_macros += [('HAVE_SWRESAMPLE', 1)]
     97        elif 'avresample' in ext.libraries:
     98            ext.define_macros += [('HAVE_AVRESAMPLE', 1)]
     99        if 'swresample' in ext.libraries or 'avresample' in ext.libraries:
     100            ext.define_macros += [('HAVE_LIBAV', 1)]
    88101    if 'sndfile' in ext.libraries:
    89102        ext.define_macros += [('HAVE_SNDFILE', 1)]
     
    108121    ext.define_macros += [('HAVE_WAVWRITE', 1)]
    109122    ext.define_macros += [('HAVE_WAVREAD', 1)]
    110     # TODO:
    111     # add cblas
     123
     124    # TODO: add cblas
    112125    if 0:
    113126        ext.libraries += ['cblas']
     
    116129def add_system_aubio(ext):
    117130    # use pkg-config to find aubio's location
    118     add_packages(['aubio'], ext)
     131    aubio_version = get_aubio_version()
     132    add_packages(['aubio = ' + aubio_version], ext)
    119133    if 'aubio' not in ext.libraries:
    120         print("Error: libaubio not found")
     134        print("Info: aubio " + aubio_version + " was not found by pkg-config")
     135    else:
     136        print("Info: using system aubio " + aubio_version + " found in " + ' '.join(ext.library_dirs))
     137
     138def add_libav_on_win(ext):
     139    """ no pkg-config on windows, simply assume these libs are available """
     140    ext.libraries += ['avformat', 'avutil', 'avcodec', 'swresample']
     141    for define_macro in ['HAVE_LIBAV', 'HAVE_SWRESAMPLE']:
     142        ext.define_macros += [(define_macro, 1)]
    121143
    122144class CleanGenerated(distutils.command.clean.clean):
    123145    def run(self):
    124         distutils.dir_util.remove_tree(output_path)
    125         distutils.command.clean.clean.run(self)
     146        if os.path.isdir(output_path):
     147            distutils.dir_util.remove_tree(output_path)
    126148
    127 class GenerateCommand(distutils.cmd.Command):
    128     description = 'generate gen/gen-*.c files from ../src/aubio.h'
    129     user_options = [
     149from distutils.command.build_ext import build_ext as _build_ext
     150class build_ext(_build_ext):
     151
     152    user_options = _build_ext.user_options + [
    130153            # The format is (long option, short option, description).
    131154            ('enable-double', None, 'use HAVE_AUBIO_DOUBLE=1 (default: 0)'),
     
    133156
    134157    def initialize_options(self):
     158        _build_ext.initialize_options(self)
    135159        self.enable_double = False
    136160
    137161    def finalize_options(self):
     162        _build_ext.finalize_options(self)
    138163        if self.enable_double:
    139164            self.announce(
     
    141166                    level=distutils.log.INFO)
    142167
    143     def run(self):
    144         self.announce( 'Generating code', level=distutils.log.INFO)
    145         generated_object_files = generate_external(header, output_path, usedouble=self.enable_double)
     168    def build_extension(self, extension):
     169        if self.enable_double or 'HAVE_AUBIO_DOUBLE' in os.environ:
     170            enable_double = True
     171        else:
     172            enable_double = False
     173        # seack for aubio headers and lib in PKG_CONFIG_PATH
     174        add_system_aubio(extension)
     175        # the lib was not installed on this system
     176        if 'aubio' not in extension.libraries:
     177            # use local src/aubio.h
     178            if os.path.isfile(os.path.join('src', 'aubio.h')):
     179                add_local_aubio_header(extension)
     180            add_local_macros(extension, usedouble=enable_double)
     181            # look for a local waf build
     182            if os.path.isfile(os.path.join('build','src', 'fvec.c.1.o')):
     183                add_local_aubio_lib(extension)
     184            else:
     185                # check for external dependencies
     186                add_external_deps(extension, usedouble=enable_double)
     187                # force adding libav on windows
     188                if os.name == 'nt' and ('WITH_LIBAV' in os.environ \
     189                        or 'CONDA_PREFIX' in os.environ):
     190                    add_libav_on_win(extension)
     191                # add libaubio sources and look for optional deps with pkg-config
     192                add_local_aubio_sources(extension)
     193        # generate files python/gen/*.c, python/gen/aubio-generated.h
     194        extension.include_dirs += [ output_path ]
     195        extension.sources += generate_external(header, output_path, overwrite = False,
     196                usedouble=enable_double)
     197        return _build_ext.build_extension(self, extension)
Note: See TracChangeset for help on using the changeset viewer.