Showing the changes to python (61 of 230 files). Show all
Unified · Split
| @@ -1,100 +1,109 @@ | ||
| 1 | −Python aubio module | |
| 2 | −=================== | |
| 1 | +aubio | |
| 2 | +===== | |
| 3 | 3 | |
| 4 | −This module wraps the aubio library for Python using the numpy module. | |
| 4 | +aubio is a collection of tools for music and audio analysis. | |
| 5 | 5 | |
| 6 | −Using the Python aubio module | |
| 7 | −----------------------------- | |
| 6 | +This package integrates the aubio library with [NumPy] to provide a set of | |
| 7 | +efficient tools to process and analyse audio signals, including: | |
| 8 | 8 | |
| 9 | −After installing python-aubio, you will be able to import the aubio module: | |
| 9 | +- read audio from any media file, including videos and remote streams | |
| 10 | +- high quality phase vocoder, spectral filterbanks, and linear filters | |
| 11 | +- Mel-Frequency Cepstrum Coefficients and standard spectral descriptors | |
| 12 | +- detection of note attacks (onset) | |
| 13 | +- pitch tracking (fundamental frequency estimation) | |
| 14 | +- beat detection and tempo tracking | |
| 10 | 15 | |
| 11 | − $ python | |
| 12 | − [...] | |
| 13 | − >>> import aubio | |
| 14 | − >>> help(aubio.miditofreq) | |
| 16 | +aubio works with both Python 2 and Python 3. | |
| 15 | 17 | |
| 16 | −Finding some inspiration | |
| 17 | −------------------------ | |
| 18 | +Links | |
| 19 | +----- | |
| 18 | 20 | |
| 19 | −Some examples are available in the `python/demos` directory. These scripts are | |
| 20 | −small programs written in python and using python-aubio. | |
| 21 | +- [module documentation][doc_python] | |
| 22 | +- [installation instructions][doc_python_install] | |
| 23 | +- [aubio manual][manual] | |
| 24 | +- [aubio homepage][homepage] | |
| 25 | +- [issue tracker][bugtracker] | |
| 21 | 26 | |
| 22 | −For instance, `demo_source.py` reads a media file. | |
| 27 | +Demos | |
| 28 | +----- | |
| 23 | 29 | |
| 24 | − $ ./python/demos/demo_source.py /path/to/sound/sample.wav | |
| 30 | +Some examples are available in the [`python/demos`][demos_dir] folder. Each | |
| 31 | +script is a command line program which accepts one ore more argument. | |
| 25 | 32 | |
| 26 | −and `demo_timestretch_online.py` stretches the original file into a new one: | |
| 33 | +**Notes**: installing additional modules is required to run some of the demos. | |
| 27 | 34 | |
| 28 | − $ ./python/demo/demo_timestretch_online.py loop.wav stretched_loop.wav 0.92` | |
| 35 | +### Analysis | |
| 29 | 36 | |
| 30 | −Note: you might need to install additional modules to run some of the demos. | |
| 31 | −Some demos use [matplotlib](http://matplotlib.org/) to draw plots, others use | |
| 32 | −[PySoundCard](https://github.com/bastibe/PySoundCard) to play and record | |
| 33 | −sounds. | |
| 37 | +- `demo_source.py` uses aubio to read audio samples from media files | |
| 38 | +- `demo_onset_plot.py` detects attacks in a sound file and plots the results | |
| 39 | + using [matplotlib] | |
| 40 | +- `demo_pitch.py` looks for fundamental frequency in a sound file and plots the | |
| 41 | + results using [matplotlib] | |
| 42 | +- `demo_spectrogram.py`, `demo_specdesc.py`, `demo_mfcc.py` for spectral | |
| 43 | + analysis. | |
| 34 | 44 | |
| 35 | −Testing the Python module | |
| 36 | −------------------------- | |
| 45 | +### Real-time | |
| 37 | 46 | |
| 38 | −To run the all the python tests, use the script: | |
| 47 | +- `demo_pyaudio.py` and `demo_tapthebeat.py` use [pyaudio] | |
| 48 | +- `demo_pysoundcard_play.py`, `demo_pysoundcard.py` use [PySoundCard] | |
| 49 | +- `demo_alsa.py` uses [pyalsaaudio] | |
| 39 | 50 | |
| 40 | − $ ./python/tests/run_all_tests | |
| 51 | +### Others | |
| 41 | 52 | |
| 42 | −Each test script can also be called one at a time. For instance: | |
| 53 | +- `demo_timestretch.py` can change the duration of an input file and write the | |
| 54 | + new sound to disk, | |
| 55 | +- `demo_wav2midi.py` detects the notes in a file and uses [mido] to write the | |
| 56 | + results into a MIDI file | |
| 43 | 57 | |
| 44 | − $ ./python/tests/test_note2midi.py -v | |
| 58 | +### Example | |
| 45 | 59 | |
| 46 | −Install in a virtualenv | |
| 47 | −----------------------- | |
| 60 | +Use `demo_timestretch_online.py` to slow down `loop.wav`, write the results in | |
| 61 | +`stretched_loop.wav`: | |
| 48 | 62 | |
| 49 | −You should be able to install python-aubio directly from the top source | |
| 50 | −directory of aubio. | |
| 63 | + $ python demo_timestretch_online.py loop.wav stretched_loop.wav 0.92 | |
| 51 | 64 | |
| 52 | −First, create a virtualenv to hold the required python module: | |
| 53 | − | |
| 54 | − $ virtualenv pyaubio | |
| 55 | − $ source pyaubio/bin/activate | |
| 56 | − | |
| 57 | −Now install and build the python extension using: | |
| 58 | − | |
| 59 | − $ pip install . | |
| 60 | − | |
| 61 | −Install requirements | |
| 62 | −-------------------- | |
| 63 | − | |
| 64 | −Before compiling this module, you must have compiled libaubio. | |
| 65 | − | |
| 66 | −A simple way to do this is with pip: | |
| 67 | − | |
| 68 | − $ pip install -r requirements.txt | |
| 69 | − | |
| 70 | −For more information about how this module works, please refer to the [Python/C | |
| 71 | −API Reference Manual] (http://docs.python.org/c-api/index.html) and the | |
| 72 | −[Numpy/C API Reference](http://docs.scipy.org/doc/numpy/reference/c-api.html). | |
| 73 | − | |
| 74 | −Compiling python aubio | |
| 75 | −---------------------- | |
| 76 | − | |
| 77 | −To build the aubio Python module, run the following command from the top source | |
| 78 | −directory of aubio: | |
| 79 | − | |
| 80 | − $ ./setup.py build | |
| 81 | − | |
| 82 | −Note: if libaubio was previously built using waf, the script will use it. | |
| 83 | −Otherwise, the entire library will be built inside the python extension. | |
| 84 | − | |
| 85 | −To find out more about `setup.py` options: | |
| 86 | − | |
| 87 | − $ ./setup.py --help | |
| 88 | − | |
| 89 | −Installing | |
| 65 | +Built with | |
| 90 | 66 | ---------- |
| 91 | 67 | |
| 92 | −To install the Python module: | |
| 93 | − | |
| 94 | − $ ./setup.py install | |
| 95 | − | |
| 96 | −Alternatively, you may want to use the Python module without installing it by | |
| 97 | −setting your PYTHONPATH, for instance as follows: | |
| 98 | − | |
| 99 | − $ export PYTHONPATH=$PYTHONPATH:$PWD/`ls -rtd build/lib.* | head -1`:$PWD/tests | |
| 100 | − | |
| 68 | +The core of aubio is written in C for portability and speed. In addition to | |
| 69 | +[NumPy], aubio can be optionally built to use one or more of the following | |
| 70 | +libraries: | |
| 71 | + | |
| 72 | +- media file reading: | |
| 73 | + | |
| 74 | + - [ffmpeg] / [avcodec] to decode and read audio from almost any format, | |
| 75 | + - [libsndfile] to read audio from uncompressed sound files, | |
| 76 | + - [libsamplerate] to re-sample audio signals, | |
| 77 | + - [CoreAudio] to read all media formats supported by macOS, iOS, and tvOS. | |
| 78 | + | |
| 79 | +- hardware acceleration: | |
| 80 | + | |
| 81 | + - [Atlas] and [Blas], for accelerated vector and matrix computations, | |
| 82 | + - [fftw3], to compute fast Fourier Transforms of any size, | |
| 83 | + - [Accelerate] for accelerated FFT and matrix computations (macOS/iOS), | |
| 84 | + - [Intel IPP], accelerated vector computation and FFT implementation. | |
| 85 | + | |
| 86 | +[ffmpeg]: https://ffmpeg.org | |
| 87 | +[avcodec]: https://libav.org | |
| 88 | +[libsndfile]: http://www.mega-nerd.com/libsndfile/ | |
| 89 | +[libsamplerate]: http://www.mega-nerd.com/SRC/ | |
| 90 | +[CoreAudio]: https://developer.apple.com/reference/coreaudio | |
| 91 | +[Atlas]: http://math-atlas.sourceforge.net/ | |
| 92 | +[Blas]: https://en.wikipedia.org/wiki/Basic_Linear_Algebra_Subprograms | |
| 93 | +[fftw3]: http://fftw.org | |
| 94 | +[Accelerate]: https://developer.apple.com/reference/accelerate | |
| 95 | +[Intel IPP]: https://software.intel.com/en-us/intel-ipp | |
| 96 | + | |
| 97 | +[demos_dir]:https://github.com/aubio/aubio/tree/master/python/demos | |
| 98 | +[pyaudio]:https://people.csail.mit.edu/hubert/pyaudio/ | |
| 99 | +[PySoundCard]:https://github.com/bastibe/PySoundCard | |
| 100 | +[pyalsaaudio]:https://larsimmisch.github.io/pyalsaaudio/ | |
| 101 | +[mido]:https://mido.readthedocs.io | |
| 102 | + | |
| 103 | +[manual]: https://aubio.org/manual/latest/ | |
| 104 | +[doc_python]: https://aubio.org/manual/latest/python.html | |
| 105 | +[doc_python_install]: https://aubio.org/manual/latest/python_module.html | |
| 106 | +[homepage]: https://aubio.org | |
| 107 | +[NumPy]: https://www.numpy.org | |
| 108 | +[bugtracker]: https://github.com/aubio/aubio/issues | |
| 109 | +[matplotlib]:https://matplotlib.org/ | |
Empty file deleted.
| @@ -0,0 +1,45 @@ | ||
| 1 | +#! /usr/bin/env python | |
| 2 | + | |
| 3 | +import alsaaudio | |
| 4 | +import numpy as np | |
| 5 | +import aubio | |
| 6 | + | |
| 7 | +# constants | |
| 8 | +samplerate = 44100 | |
| 9 | +win_s = 2048 | |
| 10 | +hop_s = win_s // 2 | |
| 11 | +framesize = hop_s | |
| 12 | + | |
| 13 | +# set up audio input | |
| 14 | +recorder = alsaaudio.PCM(type=alsaaudio.PCM_CAPTURE) | |
| 15 | +recorder.setperiodsize(framesize) | |
| 16 | +recorder.setrate(samplerate) | |
| 17 | +recorder.setformat(alsaaudio.PCM_FORMAT_FLOAT_LE) | |
| 18 | +recorder.setchannels(1) | |
| 19 | + | |
| 20 | +# create aubio pitch detection (first argument is method, "default" is | |
| 21 | +# "yinfft", can also be "yin", "mcomb", fcomb", "schmitt"). | |
| 22 | +pitcher = aubio.pitch("default", win_s, hop_s, samplerate) | |
| 23 | +# set output unit (can be 'midi', 'cent', 'Hz', ...) | |
| 24 | +pitcher.set_unit("Hz") | |
| 25 | +# ignore frames under this level (dB) | |
| 26 | +pitcher.set_silence(-40) | |
| 27 | + | |
| 28 | +print("Starting to listen, press Ctrl+C to stop") | |
| 29 | + | |
| 30 | +# main loop | |
| 31 | +while True: | |
| 32 | + try: | |
| 33 | + # read data from audio input | |
| 34 | + _, data = recorder.read() | |
| 35 | + # convert data to aubio float samples | |
| 36 | + samples = np.fromstring(data, dtype=aubio.float_type) | |
| 37 | + # pitch of current frame | |
| 38 | + freq = pitcher(samples)[0] | |
| 39 | + # compute energy of current block | |
| 40 | + energy = np.sum(samples**2)/len(samples) | |
| 41 | + # do something with the results | |
| 42 | + print("{:10.4f} {:10.4f}".format(freq,energy)) | |
| 43 | + except KeyboardInterrupt: | |
| 44 | + print("Ctrl+C pressed, exiting") | |
| 45 | + break | |
| @@ -0,0 +1,50 @@ | ||
| 1 | +#! /usr/bin/env python | |
| 2 | + | |
| 3 | +import numpy as np | |
| 4 | +from aubio import pitch | |
| 5 | +import pylab as plt | |
| 6 | + | |
| 7 | +buf_size = 2048 * 1 | |
| 8 | +hop_size = buf_size // 4 | |
| 9 | + | |
| 10 | +samplerate = 44100 | |
| 11 | +minfreq = 40 | |
| 12 | +maxfreq = 6000 | |
| 13 | + | |
| 14 | +def sinewave(freq, duration, samplerate = samplerate): | |
| 15 | + """ generate a sinewave """ | |
| 16 | + length = hop_size | |
| 17 | + while length < duration * samplerate: | |
| 18 | + length += hop_size | |
| 19 | + return np.sin( 2. * np.pi * np.arange(length) * freq / samplerate ).astype("float32") | |
| 20 | + | |
| 21 | +def get_stats_for_pitch_method(method, freqs, samplerate = samplerate): | |
| 22 | + """ for a given pitch method and a list of frequency, generate a sinewave | |
| 23 | + and get mean deviation """ | |
| 24 | + means = np.zeros(len(freqs)) | |
| 25 | + medians = np.zeros(len(freqs)) | |
| 26 | + for freq, fn in zip(freqs, range(len(freqs))): | |
| 27 | + s = sinewave(freq, .50).reshape(-1, hop_size) | |
| 28 | + #s = (sinewave(freq, .50) + .0*sinewave(freq/2., .50)).reshape(-1, hop_size) | |
| 29 | + p = pitch(method, buf_size, hop_size, samplerate = samplerate) | |
| 30 | + candidates = np.zeros(len(s)) | |
| 31 | + #samples = np.zeros(buf_size) | |
| 32 | + for frame, i in zip(s, range(len(s))): | |
| 33 | + candidates[i] = p(frame)[0] | |
| 34 | + # skip first few candidates | |
| 35 | + candidates = candidates[4:] | |
| 36 | + means[fn] = np.mean(candidates[candidates != 0] - freq) | |
| 37 | + medians[fn] = np.median(candidates[candidates != 0] - freq) | |
| 38 | + print (freq, means[fn], medians[fn]) | |
| 39 | + return means, medians | |
| 40 | + | |
| 41 | +if __name__ == '__main__': | |
| 42 | + freqs = np.arange(minfreq, maxfreq, 1.) | |
| 43 | + modes = ["yin", "yinfft"] | |
| 44 | + for mode in modes: | |
| 45 | + means, medians = get_stats_for_pitch_method(mode, freqs) | |
| 46 | + plt.figure() | |
| 47 | + plt.plot(freqs, means, 'g-') | |
| 48 | + plt.plot(freqs, medians, 'r--') | |
| 49 | + #plt.savefig(mode + '_deviations_test.png', dpi=300) | |
| 50 | + plt.show() | |
| @@ -3,26 +3,33 @@ · 2 unmodified lines | ||
| 3 | 3 | from aubio import source, tempo |
| 4 | 4 | from numpy import median, diff |
| 5 | 5 | |
| 6 | −def get_file_bpm(path, params = None): | |
| 6 | +def get_file_bpm(path, params=None): | |
| 7 | 7 | """ Calculate the beats per minute (bpm) of a given file. |
| 8 | 8 | path: path to the file |
| 9 | 9 | param: dictionary of parameters |
| 10 | 10 | """ |
| 11 | 11 | if params is None: |
| 12 | 12 | params = {} |
| 13 | − try: | |
| 14 | − win_s = params['win_s'] | |
| 15 | − samplerate = params['samplerate'] | |
| 16 | − hop_s = params['hop_s'] | |
| 17 | − except KeyError: | |
| 18 | − """ | |
| 19 | − # super fast | |
| 20 | − samplerate, win_s, hop_s = 4000, 128, 64 | |
| 21 | − # fast | |
| 22 | − samplerate, win_s, hop_s = 8000, 512, 128 | |
| 23 | − """ | |
| 24 | − # default: | |
| 25 | − samplerate, win_s, hop_s = 44100, 1024, 512 | |
| 13 | + # default: | |
| 14 | + samplerate, win_s, hop_s = 44100, 1024, 512 | |
| 15 | + if 'mode' in params: | |
| 16 | + if params.mode in ['super-fast']: | |
| 17 | + # super fast | |
| 18 | + samplerate, win_s, hop_s = 4000, 128, 64 | |
| 19 | + elif params.mode in ['fast']: | |
| 20 | + # fast | |
| 21 | + samplerate, win_s, hop_s = 8000, 512, 128 | |
| 22 | + elif params.mode in ['default']: | |
| 23 | + pass | |
| 24 | + else: | |
| 25 | + raise ValueError("unknown mode {:s}".format(params.mode)) | |
| 26 | + # manual settings | |
| 27 | + if 'samplerate' in params: | |
| 28 | + samplerate = params.samplerate | |
| 29 | + if 'win_s' in params: | |
| 30 | + win_s = params.win_s | |
| 31 | + if 'hop_s' in params: | |
| 32 | + hop_s = params.hop_s | |
| 26 | 33 | |
| 27 | 34 | s = source(path, samplerate, hop_s) |
| 28 | 35 | samplerate = s.samplerate |
| @@ -44,19 +51,29 @@ def get_file_bpm(path, params = None): · 15 unmodified lines | ||
| 44 | 51 | if read < hop_s: |
| 45 | 52 | break |
| 46 | 53 | |
| 47 | − # Convert to periods and to bpm | |
| 48 | − if len(beats) > 1: | |
| 49 | − if len(beats) < 4: | |
| 50 | − print("few beats found in {:s}".format(path)) | |
| 51 | − bpms = 60./diff(beats) | |
| 52 | − b = median(bpms) | |
| 53 | − else: | |
| 54 | − b = 0 | |
| 55 | − print("not enough beats found in {:s}".format(path)) | |
| 56 | − return b | |
| 54 | + def beats_to_bpm(beats, path): | |
| 55 | + # if enough beats are found, convert to periods then to bpm | |
| 56 | + if len(beats) > 1: | |
| 57 | + if len(beats) < 4: | |
| 58 | + print("few beats found in {:s}".format(path)) | |
| 59 | + bpms = 60./diff(beats) | |
| 60 | + return median(bpms) | |
| 61 | + else: | |
| 62 | + print("not enough beats found in {:s}".format(path)) | |
| 63 | + return 0 | |
| 64 | + | |
| 65 | + return beats_to_bpm(beats, path) | |
| 57 | 66 | |
| 58 | 67 | if __name__ == '__main__': |
| 59 | − import sys | |
| 60 | − for f in sys.argv[1:]: | |
| 61 | − bpm = get_file_bpm(f) | |
| 68 | + import argparse | |
| 69 | + parser = argparse.ArgumentParser() | |
| 70 | + parser.add_argument('-m', '--mode', | |
| 71 | + help="mode [default|fast|super-fast]", | |
| 72 | + dest="mode", default='default') | |
| 73 | + parser.add_argument('sources', | |
| 74 | + nargs='+', | |
| 75 | + help="input_files") | |
| 76 | + args = parser.parse_args() | |
| 77 | + for f in args.sources: | |
| 78 | + bpm = get_file_bpm(f, params = args) | |
| 62 | 79 | print("{:6s} {:s}".format("{:2f}".format(bpm), f)) |
| @@ -1,36 +1,53 @@ | ||
| 1 | 1 | #! /usr/bin/env python |
| 2 | 2 | |
| 3 | +import sys | |
| 4 | +import os.path | |
| 5 | +import aubio | |
| 3 | 6 | |
| 4 | −def apply_filter(path): | |
| 5 | − from aubio import source, sink, digital_filter | |
| 6 | − from os.path import basename, splitext | |
| 7 | 7 | |
| 8 | +def apply_filter(path, target): | |
| 8 | 9 | # open input file, get its samplerate |
| 9 | − s = source(path) | |
| 10 | + s = aubio.source(path) | |
| 10 | 11 | samplerate = s.samplerate |
| 11 | 12 | |
| 12 | 13 | # create an A-weighting filter |
| 13 | − f = digital_filter(7) | |
| 14 | + f = aubio.digital_filter(7) | |
| 14 | 15 | f.set_a_weighting(samplerate) |
| 15 | − # alternatively, apply another filter | |
| 16 | 16 | |
| 17 | 17 | # create output file |
| 18 | − o = sink("filtered_" + splitext(basename(path))[0] + ".wav", samplerate) | |
| 18 | + o = aubio.sink(target, samplerate) | |
| 19 | 19 | |
| 20 | 20 | total_frames = 0 |
| 21 | 21 | while True: |
| 22 | + # read from source | |
| 22 | 23 | samples, read = s() |
| 24 | + # filter samples | |
| 23 | 25 | filtered_samples = f(samples) |
| 26 | + # write to sink | |
| 24 | 27 | o(filtered_samples, read) |
| 28 | + # count frames read | |
| 25 | 29 | total_frames += read |
| 26 | − if read < s.hop_size: break | |
| 30 | + # end of file reached | |
| 31 | + if read < s.hop_size: | |
| 32 | + break | |
| 27 | 33 | |
| 34 | + # print some info | |
| 28 | 35 | duration = total_frames / float(samplerate) |
| 29 | − print ("read {:s}".format(s.uri)) | |
| 30 | − print ("applied A-weighting filtered ({:d} Hz)".format(samplerate)) | |
| 31 | − print ("wrote {:s} ({:.2f} s)".format(o.uri, duration)) | |
| 36 | + input_str = "input: {:s} ({:.2f} s, {:d} Hz)" | |
| 37 | + output_str = "output: {:s}, A-weighting filtered ({:d} frames total)" | |
| 38 | + print(input_str.format(s.uri, duration, samplerate)) | |
| 39 | + print(output_str.format(o.uri, total_frames)) | |
| 32 | 40 | |
| 33 | 41 | if __name__ == '__main__': |
| 34 | − import sys | |
| 35 | − for f in sys.argv[1:]: | |
| 36 | − apply_filter(f) | |
| 42 | + usage = "{:s} <input_file> [output_file]".format(sys.argv[0]) | |
| 43 | + if not 1 < len(sys.argv) < 4: | |
| 44 | + print(usage) | |
| 45 | + sys.exit(1) | |
| 46 | + if len(sys.argv) < 3: | |
| 47 | + input_path = sys.argv[1] | |
| 48 | + basename = os.path.splitext(os.path.basename(input_path))[0] + ".wav" | |
| 49 | + output_path = "filtered_" + basename | |
| 50 | + else: | |
| 51 | + input_path, output_path = sys.argv[1:] | |
| 52 | + # run function | |
| 53 | + apply_filter(input_path, output_path) | |
| @@ -1,30 +1,44 @@ | ||
| 1 | 1 | #! /usr/bin/env python |
| 2 | 2 | |
| 3 | −from aubio import filterbank, fvec | |
| 4 | −from pylab import loglog, show, xlim, ylim, xlabel, ylabel, title | |
| 5 | −from numpy import vstack, arange | |
| 3 | +"""Create a filterbank from a list of frequencies. | |
| 6 | 4 | |
| 7 | −win_s = 2048 | |
| 5 | +This demo uses `aubio.filterbank.set_triangle_bands` to build a set of | |
| 6 | +triangular filters from a list of frequencies. | |
| 7 | + | |
| 8 | +The filterbank coefficients are then modified before being displayed.""" | |
| 9 | + | |
| 10 | +import aubio | |
| 11 | +import numpy as np | |
| 12 | +import matplotlib.pyplot as plt | |
| 13 | + | |
| 14 | +# sampling rate and size of the fft | |
| 8 | 15 | samplerate = 48000 |
| 16 | +win_s = 2048 | |
| 9 | 17 | |
| 18 | +# define a list of custom frequency | |
| 10 | 19 | freq_list = [60, 80, 200, 400, 800, 1600, 3200, 6400, 12800, 24000] |
| 20 | +# number of filters to create | |
| 11 | 21 | n_filters = len(freq_list) - 2 |
| 12 | 22 | |
| 13 | −f = filterbank(n_filters, win_s) | |
| 14 | −freqs = fvec(freq_list) | |
| 23 | +# create a new filterbank | |
| 24 | +f = aubio.filterbank(n_filters, win_s) | |
| 25 | +freqs = aubio.fvec(freq_list) | |
| 15 | 26 | f.set_triangle_bands(freqs, samplerate) |
| 16 | 27 | |
| 28 | +# get the coefficients from the filterbank | |
| 17 | 29 | coeffs = f.get_coeffs() |
| 18 | −coeffs[4] *= 5. | |
| 19 | − | |
| 30 | +# apply a gain to fifth band | |
| 31 | +coeffs[4] *= 6. | |
| 32 | +# load the modified coeffs into the filterbank | |
| 20 | 33 | f.set_coeffs(coeffs) |
| 21 | 34 | |
| 22 | −times = vstack([arange(win_s // 2 + 1) * samplerate / win_s] * n_filters) | |
| 23 | −title('Bank of filters built using a simple list of boundaries\nThe middle band has been amplified by 2.') | |
| 24 | −loglog(times.T, f.get_coeffs().T, '.-') | |
| 25 | −xlim([50, samplerate/2]) | |
| 26 | −ylim([1.0e-6, 2.0e-2]) | |
| 27 | −xlabel('log frequency (Hz)') | |
| 28 | −ylabel('log amplitude') | |
| 29 | − | |
| 30 | −show() | |
| 35 | +# display the band gains in a loglog plot | |
| 36 | +freqs = np.vstack([np.arange(win_s // 2 + 1) * samplerate / win_s] * n_filters) | |
| 37 | +plt.title('filterbank built from a list of frequencies\n' | |
| 38 | + 'The 5th band has been amplified by a factor 6.') | |
| 39 | +plt.loglog(freqs.T, f.get_coeffs().T, '.-') | |
| 40 | +plt.xlim([50, samplerate/2]) | |
| 41 | +plt.ylim([1.0e-6, 2.0e-2]) | |
| 42 | +plt.xlabel('log frequency (Hz)') | |
| 43 | +plt.ylabel('log amplitude') | |
| 44 | +plt.show() | |
| @@ -0,0 +1,37 @@ | ||
| 1 | +#! /usr/bin/env python | |
| 2 | + | |
| 3 | +import sys | |
| 4 | +from aubio import source, notes | |
| 5 | + | |
| 6 | +if len(sys.argv) < 2: | |
| 7 | + print("Usage: %s <filename> [samplerate]" % sys.argv[0]) | |
| 8 | + sys.exit(1) | |
| 9 | + | |
| 10 | +filename = sys.argv[1] | |
| 11 | + | |
| 12 | +downsample = 1 | |
| 13 | +samplerate = 44100 // downsample | |
| 14 | +if len( sys.argv ) > 2: samplerate = int(sys.argv[2]) | |
| 15 | + | |
| 16 | +win_s = 512 // downsample # fft size | |
| 17 | +hop_s = 256 // downsample # hop size | |
| 18 | + | |
| 19 | +s = source(filename, samplerate, hop_s) | |
| 20 | +samplerate = s.samplerate | |
| 21 | + | |
| 22 | +tolerance = 0.8 | |
| 23 | + | |
| 24 | +notes_o = notes("default", win_s, hop_s, samplerate) | |
| 25 | + | |
| 26 | +print("%8s" % "time","[ start","vel","last ]") | |
| 27 | + | |
| 28 | +# total number of frames read | |
| 29 | +total_frames = 0 | |
| 30 | +while True: | |
| 31 | + samples, read = s() | |
| 32 | + new_note = notes_o(samples) | |
| 33 | + if (new_note[0] != 0): | |
| 34 | + note_str = ' '.join(["%.2f" % i for i in new_note]) | |
| 35 | + print("%.6f" % (total_frames/float(samplerate)), new_note) | |
| 36 | + total_frames += read | |
| 37 | + if read < hop_s: break | |
| @@ -37,7 +37,7 @@ freqs[ pointer : pointer + partition ] = 1480 · 36 unmodified lines | ||
| 37 | 37 | |
| 38 | 38 | pointer += partition |
| 39 | 39 | pointer += partition |
| 40 | −freqs[ pointer : pointer + partition ] = 400 + 5 * np.random.random(sin_length/8) | |
| 40 | +freqs[ pointer : pointer + partition ] = 400 + 5 * np.random.random(sin_length//8) | |
| 41 | 41 | |
| 42 | 42 | a = build_sinusoid(sin_length, freqs, samplerate) |
| 43 | 43 | |
| @@ -0,0 +1,75 @@ | ||
| 1 | +#! /usr/bin/env python | |
| 2 | + | |
| 3 | +# Use pyaudio to open the microphone and run aubio.pitch on the stream of | |
| 4 | +# incoming samples. If a filename is given as the first argument, it will | |
| 5 | +# record 5 seconds of audio to this location. Otherwise, the script will | |
| 6 | +# run until Ctrl+C is pressed. | |
| 7 | + | |
| 8 | +# Examples: | |
| 9 | +# $ ./python/demos/demo_pyaudio.py | |
| 10 | +# $ ./python/demos/demo_pyaudio.py /tmp/recording.wav | |
| 11 | + | |
| 12 | +import pyaudio | |
| 13 | +import sys | |
| 14 | +import numpy as np | |
| 15 | +import aubio | |
| 16 | + | |
| 17 | +# initialise pyaudio | |
| 18 | +p = pyaudio.PyAudio() | |
| 19 | + | |
| 20 | +# open stream | |
| 21 | +buffer_size = 1024 | |
| 22 | +pyaudio_format = pyaudio.paFloat32 | |
| 23 | +n_channels = 1 | |
| 24 | +samplerate = 44100 | |
| 25 | +stream = p.open(format=pyaudio_format, | |
| 26 | + channels=n_channels, | |
| 27 | + rate=samplerate, | |
| 28 | + input=True, | |
| 29 | + frames_per_buffer=buffer_size) | |
| 30 | + | |
| 31 | +if len(sys.argv) > 1: | |
| 32 | + # record 5 seconds | |
| 33 | + output_filename = sys.argv[1] | |
| 34 | + record_duration = 5 # exit 1 | |
| 35 | + outputsink = aubio.sink(sys.argv[1], samplerate) | |
| 36 | + total_frames = 0 | |
| 37 | +else: | |
| 38 | + # run forever | |
| 39 | + outputsink = None | |
| 40 | + record_duration = None | |
| 41 | + | |
| 42 | +# setup pitch | |
| 43 | +tolerance = 0.8 | |
| 44 | +win_s = 4096 # fft size | |
| 45 | +hop_s = buffer_size # hop size | |
| 46 | +pitch_o = aubio.pitch("default", win_s, hop_s, samplerate) | |
| 47 | +pitch_o.set_unit("midi") | |
| 48 | +pitch_o.set_tolerance(tolerance) | |
| 49 | + | |
| 50 | +print("*** starting recording") | |
| 51 | +while True: | |
| 52 | + try: | |
| 53 | + audiobuffer = stream.read(buffer_size) | |
| 54 | + signal = np.fromstring(audiobuffer, dtype=np.float32) | |
| 55 | + | |
| 56 | + pitch = pitch_o(signal)[0] | |
| 57 | + confidence = pitch_o.get_confidence() | |
| 58 | + | |
| 59 | + print("{} / {}".format(pitch,confidence)) | |
| 60 | + | |
| 61 | + if outputsink: | |
| 62 | + outputsink(signal, len(signal)) | |
| 63 | + | |
| 64 | + if record_duration: | |
| 65 | + total_frames += len(signal) | |
| 66 | + if record_duration * samplerate < total_frames: | |
| 67 | + break | |
| 68 | + except KeyboardInterrupt: | |
| 69 | + print("*** Ctrl+C pressed, exiting") | |
| 70 | + break | |
| 71 | + | |
| 72 | +print("*** done recording") | |
| 73 | +stream.stop_stream() | |
| 74 | +stream.close() | |
| 75 | +p.terminate() | |
| @@ -1,16 +1,20 @@ | ||
| 1 | 1 | #! /usr/bin/env python |
| 2 | −import sys, aubio | |
| 2 | + | |
| 3 | +"""A simple example using aubio.source.""" | |
| 4 | + | |
| 5 | +import sys | |
| 6 | +import aubio | |
| 3 | 7 | |
| 4 | 8 | samplerate = 0 # use original source samplerate |
| 5 | −hop_size = 256 # number of frames to read in one block | |
| 6 | −s = aubio.source(sys.argv[1], samplerate, hop_size) | |
| 9 | +hop_size = 256 # number of frames to read in one block | |
| 10 | +src = aubio.source(sys.argv[1], samplerate, hop_size) | |
| 7 | 11 | total_frames = 0 |
| 8 | 12 | |
| 9 | −while True: # reading loop | |
| 10 | − samples, read = s() | |
| 11 | − total_frames += read | |
| 12 | − if read < hop_size: break # end of file reached | |
| 13 | +while True: | |
| 14 | + samples, read = src() # read hop_size new samples from source | |
| 15 | + total_frames += read # increment total number of frames | |
| 16 | + if read < hop_size: # end of file reached | |
| 17 | + break | |
| 13 | 18 | |
| 14 | 19 | fmt_string = "read {:d} frames at {:d}Hz from {:s}" |
| 15 | −print (fmt_string.format(total_frames, s.samplerate, sys.argv[1])) | |
| 16 | − | |
| 20 | +print(fmt_string.format(total_frames, src.samplerate, src.uri)) | |
| @@ -0,0 +1,78 @@ | ||
| 1 | +#! /usr/bin/env python | |
| 2 | + | |
| 3 | +""" A simple demo using aubio and pyaudio to play beats in real time | |
| 4 | + | |
| 5 | +Note you will need to have pyaudio installed: `pip install pyaudio`. | |
| 6 | + | |
| 7 | +Examples: | |
| 8 | + ./demo_tapthebeat.py ~/Music/track1.ogg | |
| 9 | + | |
| 10 | +When compiled with ffmpeg/libav, you should be able to open remote streams. For | |
| 11 | +instance using youtube-dl (`pip install youtube-dl`): | |
| 12 | + | |
| 13 | + ./demo_tapthebeat.py `youtube-dl -xg https://youtu.be/zZbM9n9j3_g` | |
| 14 | + | |
| 15 | +""" | |
| 16 | + | |
| 17 | +import sys | |
| 18 | +import time | |
| 19 | +import pyaudio | |
| 20 | +import aubio | |
| 21 | +import numpy as np | |
| 22 | + | |
| 23 | +win_s = 1024 # fft size | |
| 24 | +hop_s = win_s // 2 # hop size | |
| 25 | + | |
| 26 | +# parse command line arguments | |
| 27 | +if len(sys.argv) < 2: | |
| 28 | + print("Usage: %s <filename> [samplerate]" % sys.argv[0]) | |
| 29 | + sys.exit(1) | |
| 30 | + | |
| 31 | +filename = sys.argv[1] | |
| 32 | + | |
| 33 | +samplerate = 0 | |
| 34 | +if len( sys.argv ) > 2: samplerate = int(sys.argv[2]) | |
| 35 | + | |
| 36 | +# create aubio source | |
| 37 | +a_source = aubio.source(filename, samplerate, hop_s) | |
| 38 | +samplerate = a_source.samplerate | |
| 39 | + | |
| 40 | +# create aubio tempo detection | |
| 41 | +a_tempo = aubio.tempo("default", win_s, hop_s, samplerate) | |
| 42 | + | |
| 43 | +# create a simple click sound | |
| 44 | +click = 0.7 * np.sin(2. * np.pi * np.arange(hop_s) / hop_s * samplerate / 3000.) | |
| 45 | + | |
| 46 | +# pyaudio callback | |
| 47 | +def pyaudio_callback(_in_data, _frame_count, _time_info, _status): | |
| 48 | + samples, read = a_source() | |
| 49 | + is_beat = a_tempo(samples) | |
| 50 | + if is_beat: | |
| 51 | + samples += click | |
| 52 | + #print ('tick') # avoid print in audio callback | |
| 53 | + audiobuf = samples.tobytes() | |
| 54 | + if read < hop_s: | |
| 55 | + return (audiobuf, pyaudio.paComplete) | |
| 56 | + return (audiobuf, pyaudio.paContinue) | |
| 57 | + | |
| 58 | +# create pyaudio stream with frames_per_buffer=hop_s and format=paFloat32 | |
| 59 | +p = pyaudio.PyAudio() | |
| 60 | +pyaudio_format = pyaudio.paFloat32 | |
| 61 | +frames_per_buffer = hop_s | |
| 62 | +n_channels = 1 | |
| 63 | +stream = p.open(format=pyaudio_format, channels=n_channels, rate=samplerate, | |
| 64 | + output=True, frames_per_buffer=frames_per_buffer, | |
| 65 | + stream_callback=pyaudio_callback) | |
| 66 | + | |
| 67 | +# start pyaudio stream | |
| 68 | +stream.start_stream() | |
| 69 | + | |
| 70 | +# wait for stream to finish | |
| 71 | +while stream.is_active(): | |
| 72 | + time.sleep(0.1) | |
| 73 | + | |
| 74 | +# stop pyaudio stream | |
| 75 | +stream.stop_stream() | |
| 76 | +stream.close() | |
| 77 | +# close pyaudio | |
| 78 | +p.terminate() | |
| @@ -12,7 +12,7 @@ from aubio import unwrap2pi, float_type · 11 unmodified lines | ||
| 12 | 12 | import numpy as np |
| 13 | 13 | |
| 14 | 14 | win_s = 1024 |
| 15 | −hop_s = win_s / 8 # 87.5 % overlap | |
| 15 | +hop_s = win_s // 8 # 87.5 % overlap | |
| 16 | 16 | |
| 17 | 17 | warmup = win_s // hop_s - 1 |
| 18 | 18 | |
| @@ -11,8 +11,8 @@ from aubio import source, sink, pvoc, cvec · 10 unmodified lines | ||
| 11 | 11 | from aubio import unwrap2pi, float_type |
| 12 | 12 | import numpy as np |
| 13 | 13 | |
| 14 | −win_s = 1024 | |
| 15 | −hop_s = win_s / 8 # 87.5 % overlap | |
| 14 | +win_s = 512 | |
| 15 | +hop_s = win_s // 8 # 87.5 % overlap | |
| 16 | 16 | |
| 17 | 17 | warmup = win_s // hop_s - 1 |
| 18 | 18 | |
| @@ -92,8 +92,10 @@ while True: · 73 unmodified lines | ||
| 92 | 92 | old_grain.norm = np.copy(cur_grain.norm) |
| 93 | 93 | old_grain.phas = np.copy(cur_grain.phas) |
| 94 | 94 | |
| 95 | − block_read += 1 | |
| 95 | + # until end of file | |
| 96 | 96 | if read < hop_s: break |
| 97 | + # increment block counter | |
| 98 | + block_read += 1 | |
| 97 | 99 | |
| 98 | 100 | for t in range(warmup + 2): # purge the last frames from the phase vocoder |
| 99 | 101 | new_grain.norm[:] = 0 |
| @@ -0,0 +1,77 @@ | ||
| 1 | +#! /usr/bin/env python | |
| 2 | + | |
| 3 | +# Simple demo to extract notes from a sound file, and store them in a midi file | |
| 4 | +# using mido. | |
| 5 | +# | |
| 6 | +# Install mido: `pip instal mido` | |
| 7 | +# | |
| 8 | +# Documentation: https://mido.readthedocs.io/ | |
| 9 | + | |
| 10 | +import sys | |
| 11 | +from aubio import source, notes | |
| 12 | +from mido import Message, MetaMessage, MidiFile, MidiTrack, second2tick, bpm2tempo | |
| 13 | + | |
| 14 | +if len(sys.argv) < 3: | |
| 15 | + print("Usage: %s <filename> <output> [samplerate]" % sys.argv[0]) | |
| 16 | + sys.exit(1) | |
| 17 | + | |
| 18 | +filename = sys.argv[1] | |
| 19 | +midioutput = sys.argv[2] | |
| 20 | + | |
| 21 | +downsample = 1 | |
| 22 | +samplerate = 44100 // downsample | |
| 23 | +if len( sys.argv ) > 3: samplerate = int(sys.argv[3]) | |
| 24 | + | |
| 25 | +win_s = 512 // downsample # fft size | |
| 26 | +hop_s = 256 // downsample # hop size | |
| 27 | + | |
| 28 | +s = source(filename, samplerate, hop_s) | |
| 29 | +samplerate = s.samplerate | |
| 30 | + | |
| 31 | +tolerance = 0.8 | |
| 32 | + | |
| 33 | +notes_o = notes("default", win_s, hop_s, samplerate) | |
| 34 | + | |
| 35 | +print("%8s" % "time","[ start","vel","last ]") | |
| 36 | + | |
| 37 | +# create a midi file | |
| 38 | +mid = MidiFile() | |
| 39 | +track = MidiTrack() | |
| 40 | +mid.tracks.append(track) | |
| 41 | + | |
| 42 | +ticks_per_beat = mid.ticks_per_beat # default: 480 | |
| 43 | +bpm = 120 # default midi tempo | |
| 44 | + | |
| 45 | +tempo = bpm2tempo(bpm) | |
| 46 | +track.append(MetaMessage('set_tempo', tempo=tempo)) | |
| 47 | +track.append(MetaMessage('time_signature', numerator=4, denominator=4)) | |
| 48 | + | |
| 49 | +def frames2tick(frames, samplerate=samplerate): | |
| 50 | + sec = frames / float(samplerate) | |
| 51 | + return int(second2tick(sec, ticks_per_beat, tempo)) | |
| 52 | + | |
| 53 | +last_time = 0 | |
| 54 | + | |
| 55 | +# total number of frames read | |
| 56 | +total_frames = 0 | |
| 57 | +while True: | |
| 58 | + samples, read = s() | |
| 59 | + new_note = notes_o(samples) | |
| 60 | + if (new_note[0] != 0): | |
| 61 | + note_str = ' '.join(["%.2f" % i for i in new_note]) | |
| 62 | + print("%.6f" % (total_frames/float(samplerate)), new_note) | |
| 63 | + delta = frames2tick(total_frames) - last_time | |
| 64 | + if new_note[2] > 0: | |
| 65 | + track.append(Message('note_off', note=int(new_note[2]), | |
| 66 | + velocity=127, time=0) | |
| 67 | + ) | |
| 68 | + track.append(Message('note_on', | |
| 69 | + note=int(new_note[0]), | |
| 70 | + velocity=int(new_note[1]), | |
| 71 | + time=delta) | |
| 72 | + ) | |
| 73 | + last_time = frames2tick(total_frames) | |
| 74 | + total_frames += read | |
| 75 | + if read < hop_s: break | |
| 76 | + | |
| 77 | +mid.save(midioutput) | |
| @@ -0,0 +1,175 @@ | ||
| 1 | +#! /usr/bin/env python | |
| 2 | +# -*- coding: utf8 -*- | |
| 3 | + | |
| 4 | +""" Pure python implementation of the sum of squared difference | |
| 5 | + | |
| 6 | + sqd_yin: original sum of squared difference [0] | |
| 7 | + d_t(tau) = x ⊗ kernel | |
| 8 | + sqd_yinfast: sum of squared diff using complex domain [0] | |
| 9 | + sqd_yinfftslow: tappered squared diff [1] | |
| 10 | + sqd_yinfft: modified squared diff using complex domain [1] | |
| 11 | + | |
| 12 | +[0]:http://audition.ens.fr/adc/pdf/2002_JASA_YIN.pdf | |
| 13 | +[1]:https://aubio.org/phd/ | |
| 14 | +""" | |
| 15 | + | |
| 16 | +import sys | |
| 17 | +import numpy as np | |
| 18 | +import matplotlib.pyplot as plt | |
| 19 | + | |
| 20 | +def sqd_yin(samples): | |
| 21 | + """ compute original sum of squared difference | |
| 22 | + | |
| 23 | + Brute-force computation (cost o(N**2), slow).""" | |
| 24 | + B = len(samples) | |
| 25 | + W = B//2 | |
| 26 | + yin = np.zeros(W) | |
| 27 | + for j in range(W): | |
| 28 | + for tau in range(1, W): | |
| 29 | + yin[tau] += (samples[j] - samples[j+tau])**2 | |
| 30 | + return yin | |
| 31 | + | |
| 32 | +def sqd_yinfast(samples): | |
| 33 | + """ compute approximate sum of squared difference | |
| 34 | + | |
| 35 | + Using complex convolution (fast, cost o(n*log(n)) )""" | |
| 36 | + # yin_t(tau) = (r_t(0) + r_(t+tau)(0)) - 2r_t(tau) | |
| 37 | + B = len(samples) | |
| 38 | + W = B//2 | |
| 39 | + yin = np.zeros(W) | |
| 40 | + sqdiff = np.zeros(W) | |
| 41 | + kernel = np.zeros(B) | |
| 42 | + # compute r_(t+tau)(0) | |
| 43 | + squares = samples**2 | |
| 44 | + for tau in range(W): | |
| 45 | + sqdiff[tau] = squares[tau:tau+W].sum() | |
| 46 | + # add r_t(0) | |
| 47 | + sqdiff += sqdiff[0] | |
| 48 | + # compute r_t(tau) using kernel convolution in complex domain | |
| 49 | + samples_fft = np.fft.fft(samples) | |
| 50 | + kernel[1:W+1] = samples[W-1::-1] # first half, reversed | |
| 51 | + kernel_fft = np.fft.fft(kernel) | |
| 52 | + r_t_tau = np.fft.ifft(samples_fft * kernel_fft).real[W:] | |
| 53 | + # compute yin_t(tau) | |
| 54 | + yin = sqdiff - 2 * r_t_tau | |
| 55 | + return yin | |
| 56 | + | |
| 57 | +def sqd_yintapered(samples): | |
| 58 | + """ compute tappered sum of squared difference | |
| 59 | + | |
| 60 | + Brute-force computation (cost o(N**2), slow).""" | |
| 61 | + B = len(samples) | |
| 62 | + W = B//2 | |
| 63 | + yin = np.zeros(W) | |
| 64 | + for tau in range(1, W): | |
| 65 | + for j in range(W - tau): | |
| 66 | + yin[tau] += (samples[j] - samples[j+tau])**2 | |
| 67 | + return yin | |
| 68 | + | |
| 69 | +def sqd_yinfft(samples): | |
| 70 | + """ compute yinfft modified sum of squared differences | |
| 71 | + | |
| 72 | + Very fast, improved performance in transients. | |
| 73 | + | |
| 74 | + FIXME: biased.""" | |
| 75 | + B = len(samples) | |
| 76 | + W = B//2 | |
| 77 | + yin = np.zeros(W) | |
| 78 | + def hanningz(W): | |
| 79 | + return .5 * (1. - np.cos(2. * np.pi * np.arange(W) / W)) | |
| 80 | + #win = np.ones(B) | |
| 81 | + win = hanningz(B) | |
| 82 | + sqrmag = np.zeros(B) | |
| 83 | + fftout = np.fft.fft(win*samples) | |
| 84 | + sqrmag[0] = fftout[0].real**2 | |
| 85 | + for l in range(1, W): | |
| 86 | + sqrmag[l] = fftout[l].real**2 + fftout[l].imag**2 | |
| 87 | + sqrmag[B-l] = sqrmag[l] | |
| 88 | + sqrmag[W] = fftout[W].real**2 | |
| 89 | + fftout = np.fft.fft(sqrmag) | |
| 90 | + sqrsum = 2.*sqrmag[:W + 1].sum() | |
| 91 | + yin[0] = 0 | |
| 92 | + yin[1:] = sqrsum - fftout.real[1:W] | |
| 93 | + return yin / B | |
| 94 | + | |
| 95 | +def cumdiff(yin): | |
| 96 | + """ compute the cumulative mean normalized difference """ | |
| 97 | + W = len(yin) | |
| 98 | + yin[0] = 1. | |
| 99 | + cumsum = 0. | |
| 100 | + for tau in range(1, W): | |
| 101 | + cumsum += yin[tau] | |
| 102 | + if cumsum != 0: | |
| 103 | + yin[tau] *= tau/cumsum | |
| 104 | + else: | |
| 105 | + yin[tau] = 1 | |
| 106 | + return yin | |
| 107 | + | |
| 108 | +def compute_all(x): | |
| 109 | + import time | |
| 110 | + now = time.time() | |
| 111 | + | |
| 112 | + yin = sqd_yin(x) | |
| 113 | + t1 = time.time() | |
| 114 | + print ("yin took %.2fms" % ((t1-now) * 1000.)) | |
| 115 | + | |
| 116 | + yinfast = sqd_yinfast(x) | |
| 117 | + t2 = time.time() | |
| 118 | + print ("yinfast took: %.2fms" % ((t2-t1) * 1000.)) | |
| 119 | + | |
| 120 | + yintapered = sqd_yintapered(x) | |
| 121 | + t3 = time.time() | |
| 122 | + print ("yintapered took: %.2fms" % ((t3-t2) * 1000.)) | |
| 123 | + | |
| 124 | + yinfft = sqd_yinfft(x) | |
| 125 | + t4 = time.time() | |
| 126 | + print ("yinfft took: %.2fms" % ((t4-t3) * 1000.)) | |
| 127 | + | |
| 128 | + return yin, yinfast, yintapered, yinfft | |
| 129 | + | |
| 130 | +def plot_all(yin, yinfast, yintapered, yinfft): | |
| 131 | + fig, axes = plt.subplots(nrows=2, ncols=2, sharex=True, sharey='col') | |
| 132 | + | |
| 133 | + axes[0, 0].plot(yin, label='yin') | |
| 134 | + axes[0, 0].plot(yintapered, label='yintapered') | |
| 135 | + axes[0, 0].set_ylim(bottom=0) | |
| 136 | + axes[0, 0].legend() | |
| 137 | + axes[1, 0].plot(yinfast, '-', label='yinfast') | |
| 138 | + axes[1, 0].plot(yinfft, label='yinfft') | |
| 139 | + axes[1, 0].legend() | |
| 140 | + | |
| 141 | + axes[0, 1].plot(cumdiff(yin), label='yin') | |
| 142 | + axes[0, 1].plot(cumdiff(yintapered), label='yin tapered') | |
| 143 | + axes[0, 1].set_ylim(bottom=0) | |
| 144 | + axes[0, 1].legend() | |
| 145 | + axes[1, 1].plot(cumdiff(yinfast), '-', label='yinfast') | |
| 146 | + axes[1, 1].plot(cumdiff(yinfft), label='yinfft') | |
| 147 | + axes[1, 1].legend() | |
| 148 | + | |
| 149 | + fig.tight_layout() | |
| 150 | + | |
| 151 | +testfreqs = [441., 800., 10000., 40.] | |
| 152 | + | |
| 153 | +if len(sys.argv) > 1: | |
| 154 | + testfreqs = map(float,sys.argv[1:]) | |
| 155 | + | |
| 156 | +for f in testfreqs: | |
| 157 | + print ("Comparing yin implementations for sine wave at %.fHz" % f) | |
| 158 | + samplerate = 44100. | |
| 159 | + win_s = 4096 | |
| 160 | + | |
| 161 | + x = np.cos(2.*np.pi * np.arange(win_s) * f / samplerate) | |
| 162 | + | |
| 163 | + n_times = 1#00 | |
| 164 | + for n in range(n_times): | |
| 165 | + yin, yinfast, yinfftslow, yinfft = compute_all(x) | |
| 166 | + if 0: # plot difference | |
| 167 | + plt.plot(yin-yinfast) | |
| 168 | + plt.tight_layout() | |
| 169 | + plt.show() | |
| 170 | + if 1: | |
| 171 | + plt.plot(yinfftslow-yinfft) | |
| 172 | + plt.tight_layout() | |
| 173 | + plt.show() | |
| 174 | + plot_all(yin, yinfast, yinfftslow, yinfft) | |
| 175 | +plt.show() | |
| @@ -27,7 +27,7 @@ · 26 unmodified lines | ||
| 27 | 27 | #ifdef USE_LOCAL_AUBIO |
| 28 | 28 | #include "aubio.h" |
| 29 | 29 | #else |
| 30 | −#include "aubio/aubio.h" | |
| 30 | +#include <aubio/aubio.h> | |
| 31 | 31 | #endif |
| 32 | 32 | |
| 33 | 33 | #define Py_default_vector_length 1024 |
| @@ -46,6 +46,14 @@ · 12 unmodified lines | ||
| 46 | 46 | #define AUBIO_NPY_SMPL_CHR "f" |
| 47 | 47 | #endif |
| 48 | 48 | |
| 49 | +#ifndef PATH_MAX | |
| 50 | +#ifdef MAX_PATH | |
| 51 | +#define PATH_MAX MAX_PATH | |
| 52 | +#else | |
| 53 | +#define PATH_MAX 1024 | |
| 54 | +#endif | |
| 55 | +#endif | |
| 56 | + | |
| 49 | 57 | // compat with Python < 2.6 |
| 50 | 58 | #ifndef Py_TYPE |
| 51 | 59 | #define Py_TYPE(ob) (((PyObject*)(ob))->ob_type) |
| @@ -2,77 +2,202 @@ · 1 unmodified line | ||
| 2 | 2 | #include "aubio-types.h" |
| 3 | 3 | #include "py-musicutils.h" |
| 4 | 4 | |
| 5 | +// this dummy macro is used to convince windows that a string passed as -D flag | |
| 6 | +// is just that, a string, and not a double. | |
| 7 | +#define REDEFINESTRING(x) #x | |
| 8 | +#define DEFINEDSTRING(x) REDEFINESTRING(x) | |
| 9 | + | |
| 5 | 10 | static char aubio_module_doc[] = "Python module for the aubio library"; |
| 6 | 11 | |
| 7 | 12 | static char Py_alpha_norm_doc[] = "" |
| 8 | −"alpha_norm(fvec, integer) -> float\n" | |
| 13 | +"alpha_norm(vec, alpha)\n" | |
| 14 | +"\n" | |
| 15 | +"Compute `alpha` normalisation factor of vector `vec`.\n" | |
| 9 | 16 | "\n" |
| 10 | −"Compute alpha normalisation factor on vector, given alpha\n" | |
| 17 | +"Parameters\n" | |
| 18 | +"----------\n" | |
| 19 | +"vec : fvec\n" | |
| 20 | +" input vector\n" | |
| 21 | +"alpha : float\n" | |
| 22 | +" norm factor\n" | |
| 23 | +"\n" | |
| 24 | +"Returns\n" | |
| 25 | +"-------\n" | |
| 26 | +"float\n" | |
| 27 | +" p-norm of the input vector, where `p=alpha`\n" | |
| 11 | 28 | "\n" |
| 12 | 29 | "Example\n" |
| 13 | 30 | "-------\n" |
| 14 | 31 | "\n" |
| 15 | −">>> b = alpha_norm(a, 9)"; | |
| 32 | +">>> a = aubio.fvec(np.arange(10)); alpha = 2\n" | |
| 33 | +">>> aubio.alpha_norm(a, alpha), (sum(a**alpha)/len(a))**(1./alpha)\n" | |
| 34 | +"(5.338539123535156, 5.338539126015656)\n" | |
| 35 | +"\n" | |
| 36 | +"Note\n" | |
| 37 | +"----\n" | |
| 38 | +"Computed as:\n" | |
| 39 | +"\n" | |
| 40 | +".. math::\n" | |
| 41 | +" l_{\\alpha} = \n" | |
| 42 | +" \\|\\frac{\\sum_{n=0}^{N-1}{{x_n}^{\\alpha}}}{N}\\|^{1/\\alpha}\n" | |
| 43 | +""; | |
| 16 | 44 | |
| 17 | 45 | static char Py_bintomidi_doc[] = "" |
| 18 | −"bintomidi(float, samplerate = integer, fftsize = integer) -> float\n" | |
| 46 | +"bintomidi(fftbin, samplerate, fftsize)\n" | |
| 47 | +"\n" | |
| 48 | +"Convert FFT bin to frequency in midi note, given the sampling rate\n" | |
| 49 | +"and the size of the FFT.\n" | |
| 19 | 50 | "\n" |
| 20 | −"Convert bin (float) to midi (float), given the sampling rate and the FFT size\n" | |
| 51 | +"Parameters\n" | |
| 52 | +"----------\n" | |
| 53 | +"fftbin : float\n" | |
| 54 | +" input frequency bin\n" | |
| 55 | +"samplerate : float\n" | |
| 56 | +" sampling rate of the signal\n" | |
| 57 | +"fftsize : float\n" | |
| 58 | +" size of the FFT\n" | |
| 59 | +"\n" | |
| 60 | +"Returns\n" | |
| 61 | +"-------\n" | |
| 62 | +"float\n" | |
| 63 | +" Frequency converted to midi note.\n" | |
| 21 | 64 | "\n" |
| 22 | 65 | "Example\n" |
| 23 | 66 | "-------\n" |
| 24 | 67 | "\n" |
| 25 | −">>> midi = bintomidi(float, samplerate = 44100, fftsize = 1024)"; | |
| 68 | +">>> aubio.bintomidi(10, 44100, 1024)\n" | |
| 69 | +"68.62871551513672\n" | |
| 70 | +""; | |
| 26 | 71 | |
| 27 | 72 | static char Py_miditobin_doc[] = "" |
| 28 | −"miditobin(float, samplerate = integer, fftsize = integer) -> float\n" | |
| 73 | +"miditobin(midi, samplerate, fftsize)\n" | |
| 29 | 74 | "\n" |
| 30 | −"Convert midi (float) to bin (float), given the sampling rate and the FFT size\n" | |
| 75 | +"Convert frequency in midi note to FFT bin, given the sampling rate\n" | |
| 76 | +"and the size of the FFT.\n" | |
| 31 | 77 | "\n" |
| 32 | −"Example\n" | |
| 78 | +"Parameters\n" | |
| 79 | +"----------\n" | |
| 80 | +"midi : float\n" | |
| 81 | +" input frequency, in midi note\n" | |
| 82 | +"samplerate : float\n" | |
| 83 | +" sampling rate of the signal\n" | |
| 84 | +"fftsize : float\n" | |
| 85 | +" size of the FFT\n" | |
| 86 | +"\n" | |
| 87 | +"Returns\n" | |
| 33 | 88 | "-------\n" |
| 89 | +"float\n" | |
| 90 | +" Frequency converted to FFT bin.\n" | |
| 91 | +"\n" | |
| 92 | +"Examples\n" | |
| 93 | +"--------\n" | |
| 34 | 94 | "\n" |
| 35 | −">>> bin = miditobin(midi, samplerate = 44100, fftsize = 1024)"; | |
| 95 | +">>> aubio.miditobin(69, 44100, 1024)\n" | |
| 96 | +"10.216779708862305\n" | |
| 97 | +">>> aubio.miditobin(75.08, 32000, 512)\n" | |
| 98 | +"10.002175331115723\n" | |
| 99 | +""; | |
| 36 | 100 | |
| 37 | 101 | static char Py_bintofreq_doc[] = "" |
| 38 | −"bintofreq(float, samplerate = integer, fftsize = integer) -> float\n" | |
| 102 | +"bintofreq(fftbin, samplerate, fftsize)\n" | |
| 103 | +"\n" | |
| 104 | +"Convert FFT bin to frequency in Hz, given the sampling rate\n" | |
| 105 | +"and the size of the FFT.\n" | |
| 39 | 106 | "\n" |
| 40 | −"Convert bin number (float) in frequency (Hz), given the sampling rate and the FFT size\n" | |
| 107 | +"Parameters\n" | |
| 108 | +"----------\n" | |
| 109 | +"fftbin : float\n" | |
| 110 | +" input frequency bin\n" | |
| 111 | +"samplerate : float\n" | |
| 112 | +" sampling rate of the signal\n" | |
| 113 | +"fftsize : float\n" | |
| 114 | +" size of the FFT\n" | |
| 115 | +"\n" | |
| 116 | +"Returns\n" | |
| 117 | +"-------\n" | |
| 118 | +"float\n" | |
| 119 | +" Frequency converted to Hz.\n" | |
| 41 | 120 | "\n" |
| 42 | 121 | "Example\n" |
| 43 | 122 | "-------\n" |
| 44 | 123 | "\n" |
| 45 | −">>> freq = bintofreq(bin, samplerate = 44100, fftsize = 1024)"; | |
| 124 | +">>> aubio.bintofreq(10, 44100, 1024)\n" | |
| 125 | +"430.6640625\n" | |
| 126 | +""; | |
| 46 | 127 | |
| 47 | 128 | static char Py_freqtobin_doc[] = "" |
| 48 | −"freqtobin(float, samplerate = integer, fftsize = integer) -> float\n" | |
| 129 | +"freqtobin(freq, samplerate, fftsize)\n" | |
| 49 | 130 | "\n" |
| 50 | −"Convert frequency (Hz) in bin number (float), given the sampling rate and the FFT size\n" | |
| 131 | +"Convert frequency in Hz to FFT bin, given the sampling rate\n" | |
| 132 | +"and the size of the FFT.\n" | |
| 51 | 133 | "\n" |
| 52 | −"Example\n" | |
| 134 | +"Parameters\n" | |
| 135 | +"----------\n" | |
| 136 | +"midi : float\n" | |
| 137 | +" input frequency, in midi note\n" | |
| 138 | +"samplerate : float\n" | |
| 139 | +" sampling rate of the signal\n" | |
| 140 | +"fftsize : float\n" | |
| 141 | +" size of the FFT\n" | |
| 142 | +"\n" | |
| 143 | +"Returns\n" | |
| 53 | 144 | "-------\n" |
| 145 | +"float\n" | |
| 146 | +" Frequency converted to FFT bin.\n" | |
| 147 | +"\n" | |
| 148 | +"Examples\n" | |
| 149 | +"--------\n" | |
| 54 | 150 | "\n" |
| 55 | −">>> bin = freqtobin(freq, samplerate = 44100, fftsize = 1024)"; | |
| 151 | +">>> aubio.freqtobin(440, 44100, 1024)\n" | |
| 152 | +"10.216779708862305\n" | |
| 153 | +""; | |
| 56 | 154 | |
| 57 | 155 | static char Py_zero_crossing_rate_doc[] = "" |
| 58 | −"zero_crossing_rate(fvec) -> float\n" | |
| 156 | +"zero_crossing_rate(vec)\n" | |
| 157 | +"\n" | |
| 158 | +"Compute zero-crossing rate of `vec`.\n" | |
| 59 | 159 | "\n" |
| 60 | −"Compute Zero crossing rate of a vector\n" | |
| 160 | +"Parameters\n" | |
| 161 | +"----------\n" | |
| 162 | +"vec : fvec\n" | |
| 163 | +" input vector\n" | |
| 164 | +"\n" | |
| 165 | +"Returns\n" | |
| 166 | +"-------\n" | |
| 167 | +"float\n" | |
| 168 | +" Zero-crossing rate.\n" | |
| 61 | 169 | "\n" |
| 62 | 170 | "Example\n" |
| 63 | 171 | "-------\n" |
| 64 | 172 | "\n" |
| 65 | −">>> z = zero_crossing_rate(a)"; | |
| 173 | +">>> a = np.linspace(-1., 1., 1000, dtype=aubio.float_type)\n" | |
| 174 | +">>> aubio.zero_crossing_rate(a), 1/1000\n" | |
| 175 | +"(0.0010000000474974513, 0.001)\n" | |
| 176 | +""; | |
| 66 | 177 | |
| 67 | 178 | static char Py_min_removal_doc[] = "" |
| 68 | −"min_removal(fvec) -> float\n" | |
| 179 | +"min_removal(vec)\n" | |
| 180 | +"\n" | |
| 181 | +"Remove the minimum value of a vector to each of its element.\n" | |
| 69 | 182 | "\n" |
| 70 | −"Remove the minimum value of a vector, in-place modification\n" | |
| 183 | +"Modifies the input vector in-place and returns a reference to it.\n" | |
| 184 | +"\n" | |
| 185 | +"Parameters\n" | |
| 186 | +"----------\n" | |
| 187 | +"vec : fvec\n" | |
| 188 | +" input vector\n" | |
| 189 | +"\n" | |
| 190 | +"Returns\n" | |
| 191 | +"-------\n" | |
| 192 | +"fvec\n" | |
| 193 | +" modified input vector\n" | |
| 71 | 194 | "\n" |
| 72 | 195 | "Example\n" |
| 73 | 196 | "-------\n" |
| 74 | 197 | "\n" |
| 75 | −">>> min_removal(a)"; | |
| 198 | +">>> aubio.min_removal(aubio.fvec(np.arange(1,4)))\n" | |
| 199 | +"array([0., 1., 2.], dtype=" AUBIO_NPY_SMPL_STR ")\n" | |
| 200 | +""; | |
| 76 | 201 | |
| 77 | 202 | extern void add_ufuncs ( PyObject *m ); |
| 78 | 203 | extern int generated_types_ready(void); |
| @@ -112,7 +237,9 @@ Py_bintomidi (PyObject * self, PyObject * args) · 33 unmodified lines | ||
| 112 | 237 | smpl_t input, samplerate, fftsize; |
| 113 | 238 | smpl_t output; |
| 114 | 239 | |
| 115 | − if (!PyArg_ParseTuple (args, "|" AUBIO_NPY_SMPL_CHR AUBIO_NPY_SMPL_CHR AUBIO_NPY_SMPL_CHR , &input, &samplerate, &fftsize)) { | |
| 240 | + if (!PyArg_ParseTuple (args, | |
| 241 | + "" AUBIO_NPY_SMPL_CHR AUBIO_NPY_SMPL_CHR AUBIO_NPY_SMPL_CHR, | |
| 242 | + &input, &samplerate, &fftsize)) { | |
| 116 | 243 | return NULL; |
| 117 | 244 | } |
| 118 | 245 | |
| @@ -127,7 +254,9 @@ Py_miditobin (PyObject * self, PyObject * args) · 8 unmodified lines | ||
| 127 | 254 | smpl_t input, samplerate, fftsize; |
| 128 | 255 | smpl_t output; |
| 129 | 256 | |
| 130 | − if (!PyArg_ParseTuple (args, "|" AUBIO_NPY_SMPL_CHR AUBIO_NPY_SMPL_CHR AUBIO_NPY_SMPL_CHR , &input, &samplerate, &fftsize)) { | |
| 257 | + if (!PyArg_ParseTuple (args, | |
| 258 | + "" AUBIO_NPY_SMPL_CHR AUBIO_NPY_SMPL_CHR AUBIO_NPY_SMPL_CHR, | |
| 259 | + &input, &samplerate, &fftsize)) { | |
| 131 | 260 | return NULL; |
| 132 | 261 | } |
| 133 | 262 | |
| @@ -142,7 +271,9 @@ Py_bintofreq (PyObject * self, PyObject * args) · 8 unmodified lines | ||
| 142 | 271 | smpl_t input, samplerate, fftsize; |
| 143 | 272 | smpl_t output; |
| 144 | 273 | |
| 145 | − if (!PyArg_ParseTuple (args, "|" AUBIO_NPY_SMPL_CHR AUBIO_NPY_SMPL_CHR AUBIO_NPY_SMPL_CHR, &input, &samplerate, &fftsize)) { | |
| 274 | + if (!PyArg_ParseTuple (args, | |
| 275 | + "" AUBIO_NPY_SMPL_CHR AUBIO_NPY_SMPL_CHR AUBIO_NPY_SMPL_CHR, | |
| 276 | + &input, &samplerate, &fftsize)) { | |
| 146 | 277 | return NULL; |
| 147 | 278 | } |
| 148 | 279 | |
| @@ -157,7 +288,9 @@ Py_freqtobin (PyObject * self, PyObject * args) · 8 unmodified lines | ||
| 157 | 288 | smpl_t input, samplerate, fftsize; |
| 158 | 289 | smpl_t output; |
| 159 | 290 | |
| 160 | − if (!PyArg_ParseTuple (args, "|" AUBIO_NPY_SMPL_CHR AUBIO_NPY_SMPL_CHR AUBIO_NPY_SMPL_CHR, &input, &samplerate, &fftsize)) { | |
| 291 | + if (!PyArg_ParseTuple (args, | |
| 292 | + "" AUBIO_NPY_SMPL_CHR AUBIO_NPY_SMPL_CHR AUBIO_NPY_SMPL_CHR, | |
| 293 | + &input, &samplerate, &fftsize)) { | |
| 161 | 294 | return NULL; |
| 162 | 295 | } |
| 163 | 296 | |
| @@ -237,6 +370,12 @@ static PyMethodDef aubio_methods[] = { · 73 unmodified lines | ||
| 237 | 370 | {"silence_detection", Py_aubio_silence_detection, METH_VARARGS, Py_aubio_silence_detection_doc}, |
| 238 | 371 | {"level_detection", Py_aubio_level_detection, METH_VARARGS, Py_aubio_level_detection_doc}, |
| 239 | 372 | {"window", Py_aubio_window, METH_VARARGS, Py_aubio_window_doc}, |
| 373 | + {"shift", Py_aubio_shift, METH_VARARGS, Py_aubio_shift_doc}, | |
| 374 | + {"ishift", Py_aubio_ishift, METH_VARARGS, Py_aubio_ishift_doc}, | |
| 375 | + {"hztomel", Py_aubio_hztomel, METH_VARARGS|METH_KEYWORDS, Py_aubio_hztomel_doc}, | |
| 376 | + {"meltohz", Py_aubio_meltohz, METH_VARARGS|METH_KEYWORDS, Py_aubio_meltohz_doc}, | |
| 377 | + {"hztomel_htk", Py_aubio_hztomel_htk, METH_VARARGS, Py_aubio_hztomel_htk_doc}, | |
| 378 | + {"meltohz_htk", Py_aubio_meltohz_htk, METH_VARARGS, Py_aubio_meltohz_htk_doc}, | |
| 240 | 379 | {NULL, NULL, 0, NULL} /* Sentinel */ |
| 241 | 380 | }; |
| 242 | 381 | |
| @@ -323,6 +462,7 @@ initaubio (void) · 80 unmodified lines | ||
| 323 | 462 | PyModule_AddObject (m, "sink", (PyObject *) & Py_sinkType); |
| 324 | 463 | |
| 325 | 464 | PyModule_AddStringConstant(m, "float_type", AUBIO_NPY_SMPL_STR); |
| 465 | + PyModule_AddStringConstant(m, "__version__", DEFINEDSTRING(AUBIO_VERSION)); | |
| 326 | 466 | |
| 327 | 467 | // add generated objects |
| 328 | 468 | add_generated_objects(m); |
| @@ -19,7 +19,37 @@ typedef struct · 18 unmodified lines | ||
| 19 | 19 | uint_t length; |
| 20 | 20 | } Py_cvec; |
| 21 | 21 | |
| 22 | −static char Py_cvec_doc[] = "cvec object"; | |
| 22 | +static char Py_cvec_doc[] = "" | |
| 23 | +"cvec(size)\n" | |
| 24 | +"\n" | |
| 25 | +"A container holding spectral data.\n" | |
| 26 | +"\n" | |
| 27 | +"Create one `cvec` to store the spectral information of a window\n" | |
| 28 | +"of `size` points. The data will be stored in two vectors,\n" | |
| 29 | +":attr:`phas` and :attr:`norm`, each of shape (:attr:`length`,),\n" | |
| 30 | +"with `length = size // 2 + 1`.\n" | |
| 31 | +"\n" | |
| 32 | +"Parameters\n" | |
| 33 | +"----------\n" | |
| 34 | +"size: int\n" | |
| 35 | +" Size of spectrum to create.\n" | |
| 36 | +"\n" | |
| 37 | +"Examples\n" | |
| 38 | +"--------\n" | |
| 39 | +">>> c = aubio.cvec(1024)\n" | |
| 40 | +">>> c\n" | |
| 41 | +"aubio cvec of 513 elements\n" | |
| 42 | +">>> c.length\n" | |
| 43 | +"513\n" | |
| 44 | +">>> c.norm.dtype, c.phas.dtype\n" | |
| 45 | +"(dtype('float32'), dtype('float32'))\n" | |
| 46 | +">>> c.norm.shape, c.phas.shape\n" | |
| 47 | +"((513,), (513,))\n" | |
| 48 | +"\n" | |
| 49 | +"See Also\n" | |
| 50 | +"--------\n" | |
| 51 | +"fvec, fft, pvoc\n" | |
| 52 | +""; | |
| 23 | 53 | |
| 24 | 54 | |
| 25 | 55 | PyObject * |
| @@ -142,14 +172,14 @@ Py_cvec_set_norm (Py_cvec * vec, PyObject *input, void * closure) · 116 unmodified lines | ||
| 142 | 172 | { |
| 143 | 173 | npy_intp length; |
| 144 | 174 | if (!PyAubio_IsValidVector(input)) { |
| 145 | − return 1; | |
| 175 | + return -1; | |
| 146 | 176 | } |
| 147 | 177 | length = PyArray_SIZE ((PyArrayObject *)input); |
| 148 | 178 | if (length != vec->length) { |
| 149 | 179 | PyErr_Format (PyExc_ValueError, |
| 150 | − "input array has length %ld, but cvec has length %d", length, | |
| 180 | + "input array has length %" NPY_INTP_FMT ", but cvec has length %d", length, | |
| 151 | 181 | vec->length); |
| 152 | − return 1; | |
| 182 | + return -1; | |
| 153 | 183 | } |
| 154 | 184 | |
| 155 | 185 | Py_XDECREF(vec->norm); |
| @@ -163,14 +193,14 @@ Py_cvec_set_phas (Py_cvec * vec, PyObject *input, void * closure) · 7 unmodified lines | ||
| 163 | 193 | { |
| 164 | 194 | npy_intp length; |
| 165 | 195 | if (!PyAubio_IsValidVector(input)) { |
| 166 | − return 1; | |
| 196 | + return -1; | |
| 167 | 197 | } |
| 168 | 198 | length = PyArray_SIZE ((PyArrayObject *)input); |
| 169 | 199 | if (length != vec->length) { |
| 170 | 200 | PyErr_Format (PyExc_ValueError, |
| 171 | − "input array has length %ld, but cvec has length %d", length, | |
| 201 | + "input array has length %" NPY_INTP_FMT ", but cvec has length %d", length, | |
| 172 | 202 | vec->length); |
| 173 | − return 1; | |
| 203 | + return -1; | |
| 174 | 204 | } |
| 175 | 205 | |
| 176 | 206 | Py_XDECREF(vec->phas); |
| @@ -182,7 +212,7 @@ Py_cvec_set_phas (Py_cvec * vec, PyObject *input, void * closure) · 5 unmodified lines | ||
| 182 | 212 | static PyMemberDef Py_cvec_members[] = { |
| 183 | 213 | // TODO remove READONLY flag and define getter/setter |
| 184 | 214 | {"length", T_INT, offsetof (Py_cvec, length), READONLY, |
| 185 | − "length attribute"}, | |
| 215 | + "int: Length of `norm` and `phas` vectors."}, | |
| 186 | 216 | {NULL} /* Sentinel */ |
| 187 | 217 | }; |
| 188 | 218 | |
| @@ -191,11 +221,11 @@ static PyMethodDef Py_cvec_methods[] = { · 2 unmodified lines | ||
| 191 | 221 | }; |
| 192 | 222 | |
| 193 | 223 | static PyGetSetDef Py_cvec_getseters[] = { |
| 194 | − {"norm", (getter)Py_cvec_get_norm, (setter)Py_cvec_set_norm, | |
| 195 | − "Numpy vector of shape (length,) containing the magnitude", | |
| 224 | + {"norm", (getter)Py_cvec_get_norm, (setter)Py_cvec_set_norm, | |
| 225 | + "numpy.ndarray: Vector of shape `(length,)` containing the magnitude.", | |
| 196 | 226 | NULL}, |
| 197 | − {"phas", (getter)Py_cvec_get_phas, (setter)Py_cvec_set_phas, | |
| 198 | − "Numpy vector of shape (length,) containing the phase", | |
| 227 | + {"phas", (getter)Py_cvec_get_phas, (setter)Py_cvec_set_phas, | |
| 228 | + "numpy.ndarray: Vector of shape `(length,)` containing the phase.", | |
| 199 | 229 | NULL}, |
| 200 | 230 | {NULL} /* sentinel */ |
| 201 | 231 | }; |
| @@ -94,7 +94,7 @@ Py_filterbank_do(Py_filterbank * self, PyObject * args) · 93 unmodified lines | ||
| 94 | 94 | |
| 95 | 95 | if (self->vec.length != self->win_s / 2 + 1) { |
| 96 | 96 | PyErr_Format(PyExc_ValueError, |
| 97 | − "input cvec has length %d, but fft expects length %d", | |
| 97 | + "input cvec has length %d, but filterbank expects length %d", | |
| 98 | 98 | self->vec.length, self->win_s / 2 + 1); |
| 99 | 99 | return NULL; |
| 100 | 100 | } |
| @@ -122,8 +122,8 @@ Py_filterbank_set_triangle_bands (Py_filterbank * self, PyObject *args) · 21 unmodified lines | ||
| 122 | 122 | uint_t err = 0; |
| 123 | 123 | |
| 124 | 124 | PyObject *input; |
| 125 | − uint_t samplerate; | |
| 126 | − if (!PyArg_ParseTuple (args, "OI", &input, &samplerate)) { | |
| 125 | + smpl_t samplerate; | |
| 126 | + if (!PyArg_ParseTuple (args, "O" AUBIO_NPY_SMPL_CHR, &input, &samplerate)) { | |
| 127 | 127 | return NULL; |
| 128 | 128 | } |
| 129 | 129 | |
| @@ -138,8 +138,14 @@ Py_filterbank_set_triangle_bands (Py_filterbank * self, PyObject *args) · 8 unmodified lines | ||
| 138 | 138 | err = aubio_filterbank_set_triangle_bands (self->o, |
| 139 | 139 | &(self->freqs), samplerate); |
| 140 | 140 | if (err > 0) { |
| 141 | − PyErr_SetString (PyExc_ValueError, | |
| 142 | − "error when setting filter to A-weighting"); | |
| 141 | + if (PyErr_Occurred() == NULL) { | |
| 142 | + PyErr_SetString (PyExc_ValueError, "error running set_triangle_bands"); | |
| 143 | + } else { | |
| 144 | + // change the RuntimeError into ValueError | |
| 145 | + PyObject *type, *value, *traceback; | |
| 146 | + PyErr_Fetch(&type, &value, &traceback); | |
| 147 | + PyErr_Restore(PyExc_ValueError, value, traceback); | |
| 148 | + } | |
| 143 | 149 | return NULL; |
| 144 | 150 | } |
| 145 | 151 | Py_RETURN_NONE; |
| @@ -150,15 +156,79 @@ Py_filterbank_set_mel_coeffs_slaney (Py_filterbank * self, PyObject *args) · 4 unmodified lines | ||
| 150 | 156 | { |
| 151 | 157 | uint_t err = 0; |
| 152 | 158 | |
| 153 | − uint_t samplerate; | |
| 154 | − if (!PyArg_ParseTuple (args, "I", &samplerate)) { | |
| 159 | + smpl_t samplerate; | |
| 160 | + if (!PyArg_ParseTuple (args, AUBIO_NPY_SMPL_CHR, &samplerate)) { | |
| 155 | 161 | return NULL; |
| 156 | 162 | } |
| 157 | 163 | |
| 158 | 164 | err = aubio_filterbank_set_mel_coeffs_slaney (self->o, samplerate); |
| 159 | 165 | if (err > 0) { |
| 160 | − PyErr_SetString (PyExc_ValueError, | |
| 161 | − "error when setting filter to A-weighting"); | |
| 166 | + if (PyErr_Occurred() == NULL) { | |
| 167 | + PyErr_SetString (PyExc_ValueError, "error running set_mel_coeffs_slaney"); | |
| 168 | + } else { | |
| 169 | + // change the RuntimeError into ValueError | |
| 170 | + PyObject *type, *value, *traceback; | |
| 171 | + PyErr_Fetch(&type, &value, &traceback); | |
| 172 | + PyErr_Restore(PyExc_ValueError, value, traceback); | |
| 173 | + } | |
| 174 | + return NULL; | |
| 175 | + } | |
| 176 | + Py_RETURN_NONE; | |
| 177 | +} | |
| 178 | + | |
| 179 | +static PyObject * | |
| 180 | +Py_filterbank_set_mel_coeffs (Py_filterbank * self, PyObject *args) | |
| 181 | +{ | |
| 182 | + uint_t err = 0; | |
| 183 | + | |
| 184 | + smpl_t samplerate; | |
| 185 | + smpl_t freq_min; | |
| 186 | + smpl_t freq_max; | |
| 187 | + if (!PyArg_ParseTuple (args, AUBIO_NPY_SMPL_CHR AUBIO_NPY_SMPL_CHR | |
| 188 | + AUBIO_NPY_SMPL_CHR, &samplerate, &freq_min, &freq_max)) { | |
| 189 | + return NULL; | |
| 190 | + } | |
| 191 | + | |
| 192 | + err = aubio_filterbank_set_mel_coeffs (self->o, samplerate, | |
| 193 | + freq_min, freq_max); | |
| 194 | + if (err > 0) { | |
| 195 | + if (PyErr_Occurred() == NULL) { | |
| 196 | + PyErr_SetString (PyExc_ValueError, "error running set_mel_coeffs"); | |
| 197 | + } else { | |
| 198 | + // change the RuntimeError into ValueError | |
| 199 | + PyObject *type, *value, *traceback; | |
| 200 | + PyErr_Fetch(&type, &value, &traceback); | |
| 201 | + PyErr_Restore(PyExc_ValueError, value, traceback); | |
| 202 | + } | |
| 203 | + return NULL; | |
| 204 | + } | |
| 205 | + Py_RETURN_NONE; | |
| 206 | +} | |
| 207 | + | |
| 208 | +static PyObject * | |
| 209 | +Py_filterbank_set_mel_coeffs_htk (Py_filterbank * self, PyObject *args) | |
| 210 | +{ | |
| 211 | + uint_t err = 0; | |
| 212 | + | |
| 213 | + smpl_t samplerate; | |
| 214 | + smpl_t freq_min; | |
| 215 | + smpl_t freq_max; | |
| 216 | + if (!PyArg_ParseTuple (args, AUBIO_NPY_SMPL_CHR AUBIO_NPY_SMPL_CHR | |
| 217 | + AUBIO_NPY_SMPL_CHR, &samplerate, &freq_min, &freq_max)) { | |
| 218 | + return NULL; | |
| 219 | + } | |
| 220 | + | |
| 221 | + err = aubio_filterbank_set_mel_coeffs_htk (self->o, samplerate, | |
| 222 | + freq_min, freq_max); | |
| 223 | + if (err > 0) { | |
| 224 | + if (PyErr_Occurred() == NULL) { | |
| 225 | + PyErr_SetString (PyExc_ValueError, "error running set_mel_coeffs_htk"); | |
| 226 | + } else { | |
| 227 | + // change the RuntimeError into ValueError | |
| 228 | + PyObject *type, *value, *traceback; | |
| 229 | + PyErr_Fetch(&type, &value, &traceback); | |
| 230 | + PyErr_Restore(PyExc_ValueError, value, traceback); | |
| 231 | + } | |
| 162 | 232 | return NULL; |
| 163 | 233 | } |
| 164 | 234 | Py_RETURN_NONE; |
| @@ -195,15 +265,69 @@ Py_filterbank_get_coeffs (Py_filterbank * self, PyObject *unused) · 30 unmodified lines | ||
| 195 | 265 | aubio_filterbank_get_coeffs (self->o) ); |
| 196 | 266 | } |
| 197 | 267 | |
| 268 | +static PyObject * | |
| 269 | +Py_filterbank_set_power(Py_filterbank *self, PyObject *args) | |
| 270 | +{ | |
| 271 | + smpl_t power; | |
| 272 | + | |
| 273 | + if (!PyArg_ParseTuple (args, AUBIO_NPY_SMPL_CHR, &power)) { | |
| 274 | + return NULL; | |
| 275 | + } | |
| 276 | + if(aubio_filterbank_set_power (self->o, power)) { | |
| 277 | + if (PyErr_Occurred() == NULL) { | |
| 278 | + PyErr_SetString (PyExc_ValueError, | |
| 279 | + "error running filterbank.set_power"); | |
| 280 | + } else { | |
| 281 | + // change the RuntimeError into ValueError | |
| 282 | + PyObject *type, *value, *traceback; | |
| 283 | + PyErr_Fetch(&type, &value, &traceback); | |
| 284 | + PyErr_Restore(PyExc_ValueError, value, traceback); | |
| 285 | + } | |
| 286 | + return NULL; | |
| 287 | + } | |
| 288 | + Py_RETURN_NONE; | |
| 289 | +} | |
| 290 | + | |
| 291 | +static PyObject * | |
| 292 | +Py_filterbank_set_norm(Py_filterbank *self, PyObject *args) | |
| 293 | +{ | |
| 294 | + smpl_t norm; | |
| 295 | + | |
| 296 | + if (!PyArg_ParseTuple (args, AUBIO_NPY_SMPL_CHR, &norm)) { | |
| 297 | + return NULL; | |
| 298 | + } | |
| 299 | + if(aubio_filterbank_set_norm (self->o, norm)) { | |
| 300 | + if (PyErr_Occurred() == NULL) { | |
| 301 | + PyErr_SetString (PyExc_ValueError, | |
| 302 | + "error running filterbank.set_power"); | |
| 303 | + } else { | |
| 304 | + // change the RuntimeError into ValueError | |
| 305 | + PyObject *type, *value, *traceback; | |
| 306 | + PyErr_Fetch(&type, &value, &traceback); | |
| 307 | + PyErr_Restore(PyExc_ValueError, value, traceback); | |
| 308 | + } | |
| 309 | + return NULL; | |
| 310 | + } | |
| 311 | + Py_RETURN_NONE; | |
| 312 | +} | |
| 313 | + | |
| 198 | 314 | static PyMethodDef Py_filterbank_methods[] = { |
| 199 | 315 | {"set_triangle_bands", (PyCFunction) Py_filterbank_set_triangle_bands, |
| 200 | 316 | METH_VARARGS, "set coefficients of filterbanks"}, |
| 201 | 317 | {"set_mel_coeffs_slaney", (PyCFunction) Py_filterbank_set_mel_coeffs_slaney, |
| 202 | 318 | METH_VARARGS, "set coefficients of filterbank as in Auditory Toolbox"}, |
| 319 | + {"set_mel_coeffs", (PyCFunction) Py_filterbank_set_mel_coeffs, | |
| 320 | + METH_VARARGS, "set coefficients of filterbank to linearly spaced mel scale"}, | |
| 321 | + {"set_mel_coeffs_htk", (PyCFunction) Py_filterbank_set_mel_coeffs_htk, | |
| 322 | + METH_VARARGS, "set coefficients of filterbank to linearly spaced mel scale"}, | |
| 203 | 323 | {"get_coeffs", (PyCFunction) Py_filterbank_get_coeffs, |
| 204 | 324 | METH_NOARGS, "get coefficients of filterbank"}, |
| 205 | 325 | {"set_coeffs", (PyCFunction) Py_filterbank_set_coeffs, |
| 206 | 326 | METH_VARARGS, "set coefficients of filterbank"}, |
| 327 | + {"set_power", (PyCFunction) Py_filterbank_set_power, | |
| 328 | + METH_VARARGS, "set power applied to filterbank input spectrum"}, | |
| 329 | + {"set_norm", (PyCFunction) Py_filterbank_set_norm, | |
| 330 | + METH_VARARGS, "set norm applied to filterbank input spectrum"}, | |
| 207 | 331 | {NULL} |
| 208 | 332 | }; |
| 209 | 333 | |
| @@ -133,3 +133,105 @@ Py_aubio_level_detection(PyObject *self, PyObject *args) · 132 unmodified lines | ||
| 133 | 133 | |
| 134 | 134 | return level_detection; |
| 135 | 135 | } |
| 136 | + | |
| 137 | +PyObject * | |
| 138 | +Py_aubio_shift(PyObject *self, PyObject *args) | |
| 139 | +{ | |
| 140 | + PyObject *input; | |
| 141 | + fvec_t vec; | |
| 142 | + | |
| 143 | + if (!PyArg_ParseTuple (args, "O:shift", &input)) { | |
| 144 | + return NULL; | |
| 145 | + } | |
| 146 | + | |
| 147 | + if (input == NULL) { | |
| 148 | + return NULL; | |
| 149 | + } | |
| 150 | + | |
| 151 | + if (!PyAubio_ArrayToCFvec(input, &vec)) { | |
| 152 | + return NULL; | |
| 153 | + } | |
| 154 | + | |
| 155 | + fvec_shift(&vec); | |
| 156 | + | |
| 157 | + //Py_RETURN_NONE; | |
| 158 | + return (PyObject *) PyAubio_CFvecToArray(&vec); | |
| 159 | +} | |
| 160 | + | |
| 161 | +PyObject * | |
| 162 | +Py_aubio_ishift(PyObject *self, PyObject *args) | |
| 163 | +{ | |
| 164 | + PyObject *input; | |
| 165 | + fvec_t vec; | |
| 166 | + | |
| 167 | + if (!PyArg_ParseTuple (args, "O:shift", &input)) { | |
| 168 | + return NULL; | |
| 169 | + } | |
| 170 | + | |
| 171 | + if (input == NULL) { | |
| 172 | + return NULL; | |
| 173 | + } | |
| 174 | + | |
| 175 | + if (!PyAubio_ArrayToCFvec(input, &vec)) { | |
| 176 | + return NULL; | |
| 177 | + } | |
| 178 | + | |
| 179 | + fvec_ishift(&vec); | |
| 180 | + | |
| 181 | + //Py_RETURN_NONE; | |
| 182 | + return (PyObject *) PyAubio_CFvecToArray(&vec); | |
| 183 | +} | |
| 184 | + | |
| 185 | +PyObject* | |
| 186 | +Py_aubio_hztomel(PyObject *self, PyObject *args, PyObject *kwds) | |
| 187 | +{ | |
| 188 | + smpl_t v; | |
| 189 | + PyObject *htk = NULL; | |
| 190 | + static char *kwlist[] = {"f", "htk", NULL}; | |
| 191 | + if (!PyArg_ParseTupleAndKeywords(args, kwds, AUBIO_NPY_SMPL_CHR "|O", | |
| 192 | + kwlist, &v, &htk)) | |
| 193 | + { | |
| 194 | + return NULL; | |
| 195 | + } | |
| 196 | + if (htk != NULL && PyObject_IsTrue(htk) == 1) | |
| 197 | + return Py_BuildValue(AUBIO_NPY_SMPL_CHR, aubio_hztomel_htk(v)); | |
| 198 | + else | |
| 199 | + return Py_BuildValue(AUBIO_NPY_SMPL_CHR, aubio_hztomel(v)); | |
| 200 | +} | |
| 201 | + | |
| 202 | +PyObject* | |
| 203 | +Py_aubio_meltohz(PyObject *self, PyObject *args, PyObject *kwds) | |
| 204 | +{ | |
| 205 | + smpl_t v; | |
| 206 | + PyObject *htk = NULL; | |
| 207 | + static char *kwlist[] = {"m", "htk", NULL}; | |
| 208 | + if (!PyArg_ParseTupleAndKeywords(args, kwds, AUBIO_NPY_SMPL_CHR "|O", | |
| 209 | + kwlist, &v, &htk)) | |
| 210 | + { | |
| 211 | + return NULL; | |
| 212 | + } | |
| 213 | + if (htk != NULL && PyObject_IsTrue(htk) == 1) | |
| 214 | + return Py_BuildValue(AUBIO_NPY_SMPL_CHR, aubio_meltohz_htk(v)); | |
| 215 | + else | |
| 216 | + return Py_BuildValue(AUBIO_NPY_SMPL_CHR, aubio_meltohz(v)); | |
| 217 | +} | |
| 218 | + | |
| 219 | +PyObject* | |
| 220 | +Py_aubio_hztomel_htk(PyObject *self, PyObject *args) | |
| 221 | +{ | |
| 222 | + smpl_t v; | |
| 223 | + if (!PyArg_ParseTuple(args, AUBIO_NPY_SMPL_CHR, &v)) { | |
| 224 | + return NULL; | |
| 225 | + } | |
| 226 | + return Py_BuildValue(AUBIO_NPY_SMPL_CHR, aubio_hztomel_htk(v)); | |
| 227 | +} | |
| 228 | + | |
| 229 | +PyObject* | |
| 230 | +Py_aubio_meltohz_htk(PyObject *self, PyObject *args) | |
| 231 | +{ | |
| 232 | + smpl_t v; | |
| 233 | + if (!PyArg_ParseTuple(args, AUBIO_NPY_SMPL_CHR, &v)) { | |
| 234 | + return NULL; | |
| 235 | + } | |
| 236 | + return Py_BuildValue(AUBIO_NPY_SMPL_CHR, aubio_meltohz_htk(v)); | |
| 237 | +} | |
| @@ -2,73 +2,370 @@ · 1 unmodified line | ||
| 2 | 2 | #define PY_AUBIO_MUSICUTILS_H |
| 3 | 3 | |
| 4 | 4 | static char Py_aubio_window_doc[] = "" |
| 5 | −"window(string, integer) -> fvec\n" | |
| 5 | +"window(window_type, size)\n" | |
| 6 | 6 | "\n" |
| 7 | −"Create a window\n" | |
| 7 | +"Create a window of length `size`. `window_type` should be one\n" | |
| 8 | +"of the following:\n" | |
| 8 | 9 | "\n" |
| 9 | −"Example\n" | |
| 10 | +"- `default` (same as `hanningz`).\n" | |
| 11 | +"- `ones`\n" | |
| 12 | +"- `rectangle`\n" | |
| 13 | +"- `hamming`\n" | |
| 14 | +"- `hanning`\n" | |
| 15 | +"- `hanningz` [1]_\n" | |
| 16 | +"- `blackman`\n" | |
| 17 | +"- `blackman_harris`\n" | |
| 18 | +"- `gaussian`\n" | |
| 19 | +"- `welch`\n" | |
| 20 | +"- `parzen`\n" | |
| 21 | +"\n" | |
| 22 | +"Parameters\n" | |
| 23 | +"----------\n" | |
| 24 | +"window_type : str\n" | |
| 25 | +" Type of window.\n" | |
| 26 | +"size : int\n" | |
| 27 | +" Length of window.\n" | |
| 28 | +"\n" | |
| 29 | +"Returns\n" | |
| 10 | 30 | "-------\n" |
| 31 | +"fvec\n" | |
| 32 | +" Array of shape `(length,)` containing the new window.\n" | |
| 33 | +"\n" | |
| 34 | +"See Also\n" | |
| 35 | +"--------\n" | |
| 36 | +"pvoc, fft\n" | |
| 37 | +"\n" | |
| 38 | +"Examples\n" | |
| 39 | +"--------\n" | |
| 40 | +"Compute a zero-phase Hann window on `1024` points:\n" | |
| 11 | 41 | "\n" |
| 12 | −">>> window('hanningz', 1024)\n" | |
| 42 | +">>> aubio.window('hanningz', 1024)\n" | |
| 13 | 43 | "array([ 0.00000000e+00, 9.41753387e-06, 3.76403332e-05, ...,\n" |
| 14 | −" 8.46982002e-05, 3.76403332e-05, 9.41753387e-06], dtype=float32)"; | |
| 44 | +" 8.46982002e-05, 3.76403332e-05, 9.41753387e-06], dtype=float32)\n" | |
| 45 | +"\n" | |
| 46 | +"Plot different window types with `matplotlib <https://matplotlib.org/>`_:\n" | |
| 47 | +"\n" | |
| 48 | +">>> import matplotlib.pyplot as plt\n" | |
| 49 | +">>> modes = ['default', 'ones', 'rectangle', 'hamming', 'hanning',\n" | |
| 50 | +"... 'hanningz', 'blackman', 'blackman_harris', 'gaussian',\n" | |
| 51 | +"... 'welch', 'parzen']; n = 2048\n" | |
| 52 | +">>> for m in modes: plt.plot(aubio.window(m, n), label=m)\n" | |
| 53 | +"...\n" | |
| 54 | +">>> plt.legend(); plt.show()\n" | |
| 55 | +"\n" | |
| 56 | +"Note\n" | |
| 57 | +"----\n" | |
| 58 | +"The following examples contain the equivalent source code to compute\n" | |
| 59 | +"each type of window with `NumPy <https://numpy.org>`_:\n" | |
| 60 | +"\n" | |
| 61 | +">>> n = 1024; x = np.arange(n, dtype=aubio.float_type)\n" | |
| 62 | +">>> ones = np.ones(n).astype(aubio.float_type)\n" | |
| 63 | +">>> rectangle = 0.5 * ones\n" | |
| 64 | +">>> hanning = 0.5 - 0.5 * np.cos(2 * np.pi * x / n)\n" | |
| 65 | +">>> hanningz = 0.5 * (1 - np.cos(2 * np.pi * x / n))\n" | |
| 66 | +">>> hamming = 0.54 - 0.46 * np.cos(2.*np.pi * x / (n - 1))\n" | |
| 67 | +">>> blackman = 0.42 \\\n" | |
| 68 | +"... - 0.50 * np.cos(2 * np.pi * x / (n - 1)) \\\n" | |
| 69 | +"... + 0.08 * np.cos(4 * np.pi * x / (n - 1))\n" | |
| 70 | +">>> blackman_harris = 0.35875 \\\n" | |
| 71 | +"... - 0.48829 * np.cos(2 * np.pi * x / (n - 1)) \\\n" | |
| 72 | +"... + 0.14128 * np.cos(4 * np.pi * x / (n - 1)) \\\n" | |
| 73 | +"... + 0.01168 * np.cos(6 * np.pi * x / (n - 1))\n" | |
| 74 | +">>> gaussian = np.exp( - 0.5 * ((x - 0.5 * (n - 1)) \\\n" | |
| 75 | +"... / (0.25 * (n - 1)) )**2 )\n" | |
| 76 | +">>> welch = 1 - ((2 * x - n) / (n + 1))**2\n" | |
| 77 | +">>> parzen = 1 - np.abs((2 * x - n) / (n + 1))\n" | |
| 78 | +">>> default = hanningz\n" | |
| 79 | +"References\n" | |
| 80 | +"----------\n" | |
| 81 | +#if 0 | |
| 82 | +"`Window function <https://en.wikipedia.org/wiki/Window_function>`_ on\n" | |
| 83 | +"Wikipedia.\n" | |
| 84 | +"\n" | |
| 85 | +#endif | |
| 86 | +".. [1] Amalia de Götzen, Nicolas Bernardini, and Daniel Arfib. Traditional\n" | |
| 87 | +" (?) implementations of a phase vocoder: the tricks of the trade.\n" | |
| 88 | +" In *Proceedings of the International Conference on Digital Audio\n" | |
| 89 | +" Effects* (DAFx-00), pages 37–44, University of Verona, Italy, 2000.\n" | |
| 90 | +" (`online version <" | |
| 91 | +"https://www.cs.princeton.edu/courses/archive/spr09/cos325/Bernardini.pdf" | |
| 92 | +">`_).\n" | |
| 93 | +""; | |
| 15 | 94 | |
| 16 | 95 | PyObject * Py_aubio_window(PyObject *self, PyObject *args); |
| 17 | 96 | |
| 18 | 97 | static char Py_aubio_level_lin_doc[] = "" |
| 19 | −"level_lin(fvec) -> fvec\n" | |
| 98 | +"level_lin(x)\n" | |
| 20 | 99 | "\n" |
| 21 | −"Compute sound level on a linear scale.\n" | |
| 100 | +"Compute sound pressure level of `x`, on a linear scale.\n" | |
| 22 | 101 | "\n" |
| 23 | −"This gives the average of the square amplitudes.\n" | |
| 102 | +"Parameters\n" | |
| 103 | +"----------\n" | |
| 104 | +"x : fvec\n" | |
| 105 | +" input vector\n" | |
| 106 | +"\n" | |
| 107 | +"Returns\n" | |
| 108 | +"-------\n" | |
| 109 | +"float\n" | |
| 110 | +" Linear level of `x`.\n" | |
| 24 | 111 | "\n" |
| 25 | 112 | "Example\n" |
| 26 | 113 | "-------\n" |
| 27 | 114 | "\n" |
| 28 | −">>> level_Lin(numpy.ones(1024))\n" | |
| 29 | −"1.0"; | |
| 115 | +">>> aubio.level_lin(aubio.fvec(numpy.ones(1024)))\n" | |
| 116 | +"1.0\n" | |
| 117 | +"\n" | |
| 118 | +"Note\n" | |
| 119 | +"----\n" | |
| 120 | +"Computed as the average of the squared amplitudes:\n" | |
| 121 | +"\n" | |
| 122 | +".. math:: L = \\frac {\\sum_{n=0}^{N-1} {x_n}^2} {N}\n" | |
| 123 | +"\n" | |
| 124 | +"See Also\n" | |
| 125 | +"--------\n" | |
| 126 | +"db_spl, silence_detection, level_detection\n" | |
| 127 | +""; | |
| 30 | 128 | |
| 31 | 129 | PyObject * Py_aubio_level_lin(PyObject *self, PyObject *args); |
| 32 | 130 | |
| 33 | 131 | static char Py_aubio_db_spl_doc[] = "" |
| 34 | −"Compute sound pressure level (SPL) in dB\n" | |
| 132 | +"db_spl(x)\n" | |
| 35 | 133 | "\n" |
| 36 | −"This quantity is often wrongly called 'loudness'.\n" | |
| 134 | +"Compute Sound Pressure Level (SPL) of `x`, in dB.\n" | |
| 37 | 135 | "\n" |
| 38 | −"This gives ten times the log10 of the average of the square amplitudes.\n" | |
| 136 | +"Parameters\n" | |
| 137 | +"----------\n" | |
| 138 | +"x : fvec\n" | |
| 139 | +" input vector\n" | |
| 140 | +"\n" | |
| 141 | +"Returns\n" | |
| 142 | +"-------\n" | |
| 143 | +"float\n" | |
| 144 | +" Level of `x`, in dB SPL.\n" | |
| 39 | 145 | "\n" |
| 40 | 146 | "Example\n" |
| 41 | 147 | "-------\n" |
| 42 | 148 | "\n" |
| 43 | −">>> db_spl(numpy.ones(1024))\n" | |
| 44 | −"1.0"; | |
| 149 | +">>> aubio.db_spl(aubio.fvec(np.ones(1024)))\n" | |
| 150 | +"1.0\n" | |
| 151 | +">>> aubio.db_spl(0.7*aubio.fvec(np.ones(32)))\n" | |
| 152 | +"-3.098040819168091\n" | |
| 153 | +"\n" | |
| 154 | +"Note\n" | |
| 155 | +"----\n" | |
| 156 | +"Computed as `log10` of :py:func:`level_lin`:\n" | |
| 157 | +"\n" | |
| 158 | +".. math::\n" | |
| 159 | +"\n" | |
| 160 | +" {SPL}_{dB} = log10{\\frac {\\sum_{n=0}^{N-1}{x_n}^2} {N}}\n" | |
| 161 | +"\n" | |
| 162 | +"This quantity is often incorrectly called 'loudness'.\n" | |
| 163 | +"\n" | |
| 164 | +"See Also\n" | |
| 165 | +"--------\n" | |
| 166 | +"level_lin, silence_detection, level_detection\n" | |
| 167 | +""; | |
| 45 | 168 | |
| 46 | 169 | PyObject * Py_aubio_db_spl(PyObject *self, PyObject *args); |
| 47 | 170 | |
| 48 | 171 | static char Py_aubio_silence_detection_doc[] = "" |
| 49 | −"Check if buffer level in dB SPL is under a given threshold\n" | |
| 172 | +"silence_detection(vec, level)\n" | |
| 50 | 173 | "\n" |
| 51 | −"Return 0 if level is under the given threshold, 1 otherwise.\n" | |
| 174 | +"Check if level of `vec`, in dB SPL, is under a given threshold.\n" | |
| 52 | 175 | "\n" |
| 53 | −"Example\n" | |
| 176 | +"Parameters\n" | |
| 177 | +"----------\n" | |
| 178 | +"vec : fvec\n" | |
| 179 | +" input vector\n" | |
| 180 | +"level : float\n" | |
| 181 | +" level threshold, in dB SPL\n" | |
| 182 | +"\n" | |
| 183 | +"Returns\n" | |
| 54 | 184 | "-------\n" |
| 185 | +"int\n" | |
| 186 | +" `1` if level of `vec`, in dB SPL, is under `level`,\n" | |
| 187 | +" `0` otherwise.\n" | |
| 55 | 188 | "\n" |
| 56 | −">>> import numpy\n""" | |
| 57 | −">>> silence_detection(numpy.ones(1024, dtype=\"float32\"), -80)\n" | |
| 58 | −"0"; | |
| 189 | +"Examples\n" | |
| 190 | +"--------\n" | |
| 191 | +"\n" | |
| 192 | +">>> aubio.silence_detection(aubio.fvec(32), -100.)\n" | |
| 193 | +"1\n" | |
| 194 | +">>> aubio.silence_detection(aubio.fvec(np.ones(32)), 0.)\n" | |
| 195 | +"0\n" | |
| 196 | +"\n" | |
| 197 | +"See Also\n" | |
| 198 | +"--------\n" | |
| 199 | +"level_detection, db_spl, level_lin\n" | |
| 200 | +""; | |
| 59 | 201 | |
| 60 | 202 | PyObject * Py_aubio_silence_detection(PyObject *self, PyObject *args); |
| 61 | 203 | |
| 62 | 204 | static char Py_aubio_level_detection_doc[] = "" |
| 63 | −"Get buffer level in dB SPL if over a given threshold, 1. otherwise.\n" | |
| 205 | +"level_detection(vec, level)\n" | |
| 206 | +"\n" | |
| 207 | +"Check if `vec` is above threshold `level`, in dB SPL.\n" | |
| 208 | +"\n" | |
| 209 | +"Parameters\n" | |
| 210 | +"----------\n" | |
| 211 | +"vec : fvec\n" | |
| 212 | +" input vector\n" | |
| 213 | +"level : float\n" | |
| 214 | +" level threshold, in dB SPL\n" | |
| 215 | +"\n" | |
| 216 | +"Returns\n" | |
| 217 | +"-------\n" | |
| 218 | +"float\n" | |
| 219 | +" `1.0` if level of `vec` in dB SPL is under `level`,\n" | |
| 220 | +" `db_spl(vec)` otherwise.\n" | |
| 64 | 221 | "\n" |
| 65 | 222 | "Example\n" |
| 66 | 223 | "-------\n" |
| 67 | 224 | "\n" |
| 68 | −">>> import numpy\n""" | |
| 69 | −">>> level_detection(0.7*numpy.ones(1024, dtype=\"float32\"), -80)\n" | |
| 70 | −"0"; | |
| 225 | +">>> aubio.level_detection(0.7*aubio.fvec(np.ones(1024)), -3.)\n" | |
| 226 | +"1.0\n" | |
| 227 | +">>> aubio.level_detection(0.7*aubio.fvec(np.ones(1024)), -4.)\n" | |
| 228 | +"-3.0980708599090576\n" | |
| 229 | +"\n" | |
| 230 | +"See Also\n" | |
| 231 | +"--------\n" | |
| 232 | +"silence_detection, db_spl, level_lin\n" | |
| 233 | +""; | |
| 71 | 234 | |
| 72 | 235 | PyObject * Py_aubio_level_detection(PyObject *self, PyObject *args); |
| 73 | 236 | |
| 237 | +static char Py_aubio_shift_doc[] = "" | |
| 238 | +"shift(vec)\n" | |
| 239 | +"\n" | |
| 240 | +"Swap left and right partitions of a vector, in-place.\n" | |
| 241 | +"\n" | |
| 242 | +"Parameters\n" | |
| 243 | +"----------\n" | |
| 244 | +"vec : fvec\n" | |
| 245 | +" input vector to shift\n" | |
| 246 | +"\n" | |
| 247 | +"Returns\n" | |
| 248 | +"-------\n" | |
| 249 | +"fvec\n" | |
| 250 | +" The swapped vector.\n" | |
| 251 | +"\n" | |
| 252 | +"Notes\n" | |
| 253 | +"-----\n" | |
| 254 | +"The input vector is also modified.\n" | |
| 255 | +"\n" | |
| 256 | +"For a vector of length N, the partition is split at index N - N//2.\n" | |
| 257 | +"\n" | |
| 258 | +"Example\n" | |
| 259 | +"-------\n" | |
| 260 | +"\n" | |
| 261 | +">>> aubio.shift(aubio.fvec(np.arange(3)))\n" | |
| 262 | +"array([2., 0., 1.], dtype=" AUBIO_NPY_SMPL_STR ")\n" | |
| 263 | +"\n" | |
| 264 | +"See Also\n" | |
| 265 | +"--------\n" | |
| 266 | +"ishift\n" | |
| 267 | +""; | |
| 268 | +PyObject * Py_aubio_shift(PyObject *self, PyObject *args); | |
| 269 | + | |
| 270 | +static char Py_aubio_ishift_doc[] = "" | |
| 271 | +"ishift(vec)\n" | |
| 272 | +"\n" | |
| 273 | +"Swap right and left partitions of a vector, in-place.\n" | |
| 274 | +"\n" | |
| 275 | +"Parameters\n" | |
| 276 | +"----------\n" | |
| 277 | +"vec : fvec\n" | |
| 278 | +" input vector to shift\n" | |
| 279 | +"\n" | |
| 280 | +"Returns\n" | |
| 281 | +"-------\n" | |
| 282 | +"fvec\n" | |
| 283 | +" The swapped vector.\n" | |
| 284 | +"\n" | |
| 285 | +"Notes\n" | |
| 286 | +"-----\n" | |
| 287 | +"The input vector is also modified.\n" | |
| 288 | +"\n" | |
| 289 | +"Unlike with :py:func:`shift`, the partition is split at index N//2.\n" | |
| 290 | +"\n" | |
| 291 | +"Example\n" | |
| 292 | +"-------\n" | |
| 293 | +"\n" | |
| 294 | +">>> aubio.ishift(aubio.fvec(np.arange(3)))\n" | |
| 295 | +"array([1., 2., 0.], dtype=" AUBIO_NPY_SMPL_STR ")\n" | |
| 296 | +"\n" | |
| 297 | +"See Also\n" | |
| 298 | +"--------\n" | |
| 299 | +"shift\n" | |
| 300 | +""; | |
| 301 | +PyObject * Py_aubio_ishift(PyObject *self, PyObject *args); | |
| 302 | + | |
| 303 | +static char Py_aubio_hztomel_doc[] = "" | |
| 304 | +"hztomel(f, htk=False)\n" | |
| 305 | +"\n" | |
| 306 | +"Convert a scalar from frequency to mel scale.\n" | |
| 307 | +"\n" | |
| 308 | +"Parameters\n" | |
| 309 | +"----------\n" | |
| 310 | +"m : float\n" | |
| 311 | +" input frequency, in Hz\n" | |
| 312 | +"htk : bool\n" | |
| 313 | +" if `True`, use Htk mel scale instead of Slaney.\n" | |
| 314 | +"\n" | |
| 315 | +"Returns\n" | |
| 316 | +"-------\n" | |
| 317 | +"float\n" | |
| 318 | +" output mel\n" | |
| 319 | +"\n" | |
| 320 | +"See Also\n" | |
| 321 | +"--------\n" | |
| 322 | +"meltohz\n" | |
| 323 | +""; | |
| 324 | +PyObject * Py_aubio_hztomel(PyObject *self, PyObject *args); | |
| 325 | + | |
| 326 | +static char Py_aubio_meltohz_doc[] = "" | |
| 327 | +"meltohz(m, htk=False)\n" | |
| 328 | +"\n" | |
| 329 | +"Convert a scalar from mel scale to frequency.\n" | |
| 330 | +"\n" | |
| 331 | +"Parameters\n" | |
| 332 | +"----------\n" | |
| 333 | +"m : float\n" | |
| 334 | +" input mel\n" | |
| 335 | +"htk : bool\n" | |
| 336 | +" if `True`, use Htk mel scale instead of Slaney.\n" | |
| 337 | +"\n" | |
| 338 | +"Returns\n" | |
| 339 | +"-------\n" | |
| 340 | +"float\n" | |
| 341 | +" output frequency, in Hz\n" | |
| 342 | +"\n" | |
| 343 | +"See Also\n" | |
| 344 | +"--------\n" | |
| 345 | +"hztomel\n" | |
| 346 | +""; | |
| 347 | +PyObject * Py_aubio_meltohz(PyObject *self, PyObject *args); | |
| 348 | + | |
| 349 | +static char Py_aubio_hztomel_htk_doc[] = "" | |
| 350 | +"hztomel_htk(m)\n" | |
| 351 | +"\n" | |
| 352 | +"Same as `hztomel(m, htk=True)`\n" | |
| 353 | +"\n" | |
| 354 | +"See Also\n" | |
| 355 | +"--------\n" | |
| 356 | +"hztomel\n" | |
| 357 | +""; | |
| 358 | +PyObject * Py_aubio_hztomel_htk(PyObject *self, PyObject *args); | |
| 359 | + | |
| 360 | +static char Py_aubio_meltohz_htk_doc[] = "" | |
| 361 | +"meltohz_htk(m)\n" | |
| 362 | +"\n" | |
| 363 | +"Same as `meltohz(m, htk=True)`\n" | |
| 364 | +"\n" | |
| 365 | +"See Also\n" | |
| 366 | +"--------\n" | |
| 367 | +"meltohz\n" | |
| 368 | +""; | |
| 369 | +PyObject * Py_aubio_meltohz_htk(PyObject *self, PyObject *args); | |
| 370 | + | |
| 74 | 371 | #endif /* PY_AUBIO_MUSICUTILS_H */ |
| @@ -1,6 +1,58 @@ | ||
| 1 | 1 | #include "aubio-types.h" |
| 2 | 2 | |
| 3 | −static char Py_pvoc_doc[] = "pvoc object"; | |
| 3 | +static char Py_pvoc_doc[] = "" | |
| 4 | +"pvoc(win_s=512, hop_s=256)\n" | |
| 5 | +"\n" | |
| 6 | +"Phase vocoder.\n" | |
| 7 | +"\n" | |
| 8 | +"`pvoc` creates callable object implements a phase vocoder [1]_,\n" | |
| 9 | +"using the tricks detailed in [2]_.\n" | |
| 10 | +"\n" | |
| 11 | +"The call function takes one input of type `fvec` and of size\n" | |
| 12 | +"`hop_s`, and returns a `cvec` of length `win_s//2+1`.\n" | |
| 13 | +"\n" | |
| 14 | +"Parameters\n" | |
| 15 | +"----------\n" | |
| 16 | +"win_s : int\n" | |
| 17 | +" number of channels in the phase-vocoder.\n" | |
| 18 | +"hop_s : int\n" | |
| 19 | +" number of samples expected between each call\n" | |
| 20 | +"\n" | |
| 21 | +"Examples\n" | |
| 22 | +"--------\n" | |
| 23 | +">>> x = aubio.fvec(256)\n" | |
| 24 | +">>> pv = aubio.pvoc(512, 256)\n" | |
| 25 | +">>> pv(x)\n" | |
| 26 | +"aubio cvec of 257 elements\n" | |
| 27 | +"\n" | |
| 28 | +"Default values for hop_s and win_s are provided:\n" | |
| 29 | +"\n" | |
| 30 | +">>> pv = aubio.pvoc()\n" | |
| 31 | +">>> pv.win_s, pv.hop_s\n" | |
| 32 | +"512, 256\n" | |
| 33 | +"\n" | |
| 34 | +"A `cvec` can be resynthesised using `rdo()`:\n" | |
| 35 | +"\n" | |
| 36 | +">>> pv = aubio.pvoc(512, 256)\n" | |
| 37 | +">>> y = aubio.cvec(512)\n" | |
| 38 | +">>> x_reconstructed = pv.rdo(y)\n" | |
| 39 | +">>> x_reconstructed.shape\n" | |
| 40 | +"(256,)\n" | |
| 41 | +"\n" | |
| 42 | +"References\n" | |
| 43 | +"----------\n" | |
| 44 | +".. [1] James A. Moorer. The use of the phase vocoder in computer music\n" | |
| 45 | +" applications. `Journal of the Audio Engineering Society`,\n" | |
| 46 | +" 26(1/2):42–45, 1978.\n" | |
| 47 | +".. [2] Amalia de Götzen, Nicolas Bernardini, and Daniel Arfib. Traditional\n" | |
| 48 | +" (?) implementations of a phase vocoder: the tricks of the trade.\n" | |
| 49 | +" In `Proceedings of the International Conference on Digital Audio\n" | |
| 50 | +" Effects` (DAFx-00), pages 37–44, University of Verona, Italy, 2000.\n" | |
| 51 | +" (`online version <" | |
| 52 | +"https://www.cs.princeton.edu/courses/archive/spr09/cos325/Bernardini.pdf" | |
| 53 | +">`_).\n" | |
| 54 | +""; | |
| 55 | + | |
| 4 | 56 | |
| 5 | 57 | typedef struct |
| 6 | 58 | { |
| @@ -38,10 +90,6 @@ Py_pvoc_new (PyTypeObject * type, PyObject * args, PyObject * kwds) · 31 unmodified lines | ||
| 38 | 90 | self->win_s = Py_default_vector_length; |
| 39 | 91 | self->hop_s = Py_default_vector_length/2; |
| 40 | 92 | |
| 41 | − if (self == NULL) { | |
| 42 | − return NULL; | |
| 43 | − } | |
| 44 | − | |
| 45 | 93 | if (win_s > 0) { |
| 46 | 94 | self->win_s = win_s; |
| 47 | 95 | } else if (win_s < 0) { |
| @@ -121,9 +169,11 @@ Py_pvoc_do(Py_pvoc * self, PyObject * args) · 73 unmodified lines | ||
| 121 | 169 | |
| 122 | 170 | static PyMemberDef Py_pvoc_members[] = { |
| 123 | 171 | {"win_s", T_INT, offsetof (Py_pvoc, win_s), READONLY, |
| 124 | − "size of the window"}, | |
| 172 | + "int: Size of phase vocoder analysis windows, in samples.\n" | |
| 173 | + ""}, | |
| 125 | 174 | {"hop_s", T_INT, offsetof (Py_pvoc, hop_s), READONLY, |
| 126 | − "size of the hop"}, | |
| 175 | + "int: Interval between two analysis, in samples.\n" | |
| 176 | + ""}, | |
| 127 | 177 | { NULL } // sentinel |
| 128 | 178 | }; |
| 129 | 179 | |
| @@ -155,9 +205,67 @@ Py_pvoc_rdo(Py_pvoc * self, PyObject * args) · 25 unmodified lines | ||
| 155 | 205 | return self->routput; |
| 156 | 206 | } |
| 157 | 207 | |
| 208 | +static PyObject * | |
| 209 | +Pyaubio_pvoc_set_window (Py_pvoc *self, PyObject *args) | |
| 210 | +{ | |
| 211 | + uint_t err = 0; | |
| 212 | + char_t *window = NULL; | |
| 213 | + | |
| 214 | + if (!PyArg_ParseTuple (args, "s", &window)) { | |
| 215 | + return NULL; | |
| 216 | + } | |
| 217 | + err = aubio_pvoc_set_window (self->o, window); | |
| 218 | + | |
| 219 | + if (err > 0) { | |
| 220 | + PyErr_SetString (PyExc_ValueError, "error running aubio_pvoc_set_window"); | |
| 221 | + return NULL; | |
| 222 | + } | |
| 223 | + Py_RETURN_NONE; | |
| 224 | +} | |
| 225 | + | |
| 158 | 226 | static PyMethodDef Py_pvoc_methods[] = { |
| 159 | 227 | {"rdo", (PyCFunction) Py_pvoc_rdo, METH_VARARGS, |
| 160 | − "synthesis of spectral grain"}, | |
| 228 | + "rdo(fftgrain)\n" | |
| 229 | + "\n" | |
| 230 | + "Read a new spectral grain and resynthesise the next `hop_s`\n" | |
| 231 | + "output samples.\n" | |
| 232 | + "\n" | |
| 233 | + "Parameters\n" | |
| 234 | + "----------\n" | |
| 235 | + "fftgrain : cvec\n" | |
| 236 | + " new input `cvec` to synthesize from, should be of size `win_s/2+1`\n" | |
| 237 | + "\n" | |
| 238 | + "Returns\n" | |
| 239 | + "-------\n" | |
| 240 | + "fvec\n" | |
| 241 | + " re-synthesised output of shape `(hop_s,)`\n" | |
| 242 | + "\n" | |
| 243 | + "Example\n" | |
| 244 | + "-------\n" | |
| 245 | + ">>> pv = aubio.pvoc(2048, 512)\n" | |
| 246 | + ">>> out = pv.rdo(aubio.cvec(2048))\n" | |
| 247 | + ">>> out.shape\n" | |
| 248 | + "(512,)\n" | |
| 249 | + ""}, | |
| 250 | + {"set_window", (PyCFunction) Pyaubio_pvoc_set_window, METH_VARARGS, | |
| 251 | + "set_window(window_type)\n" | |
| 252 | + "\n" | |
| 253 | + "Set window function\n" | |
| 254 | + "\n" | |
| 255 | + "Parameters\n" | |
| 256 | + "----------\n" | |
| 257 | + "window_type : str\n" | |
| 258 | + " the window type to use for this phase vocoder\n" | |
| 259 | + "\n" | |
| 260 | + "Raises\n" | |
| 261 | + "------\n" | |
| 262 | + "ValueError\n" | |
| 263 | + " If an unknown window type was given.\n" | |
| 264 | + "\n" | |
| 265 | + "See Also\n" | |
| 266 | + "--------\n" | |
| 267 | + "window : create a window.\n" | |
| 268 | + ""}, | |
| 161 | 269 | {NULL} |
| 162 | 270 | }; |
| 163 | 271 | |
| @@ -12,53 +12,78 @@ typedef struct · 11 unmodified lines | ||
| 12 | 12 | } Py_sink; |
| 13 | 13 | |
| 14 | 14 | static char Py_sink_doc[] = "" |
| 15 | −" __new__(path, samplerate = 44100, channels = 1)\n" | |
| 15 | +"sink(path, samplerate=44100, channels=1)\n" | |
| 16 | 16 | "\n" |
| 17 | −" Create a new sink, opening the given path for writing.\n" | |
| 17 | +"Write audio samples to file.\n" | |
| 18 | 18 | "\n" |
| 19 | −" Examples\n" | |
| 20 | −" --------\n" | |
| 19 | +"Parameters\n" | |
| 20 | +"----------\n" | |
| 21 | +"path : str\n" | |
| 22 | +" Pathname of the file to be opened for writing.\n" | |
| 23 | +"samplerate : int\n" | |
| 24 | +" Sampling rate of the file, in Hz.\n" | |
| 25 | +"channels : int\n" | |
| 26 | +" Number of channels to create the file with.\n" | |
| 21 | 27 | "\n" |
| 22 | −" Create a new sink at 44100Hz, mono:\n" | |
| 28 | +"Examples\n" | |
| 29 | +"--------\n" | |
| 23 | 30 | "\n" |
| 24 | −" >>> sink('/tmp/t.wav')\n" | |
| 31 | +"Create a new sink at 44100Hz, mono:\n" | |
| 25 | 32 | "\n" |
| 26 | −" Create a new sink at 8000Hz, mono:\n" | |
| 33 | +">>> snk = aubio.sink('out.wav')\n" | |
| 27 | 34 | "\n" |
| 28 | −" >>> sink('/tmp/t.wav', samplerate = 8000)\n" | |
| 35 | +"Create a new sink at 32000Hz, stereo, write 100 samples into it:\n" | |
| 29 | 36 | "\n" |
| 30 | −" Create a new sink at 32000Hz, stereo:\n" | |
| 37 | +">>> snk = aubio.sink('out.wav', samplerate=16000, channels=3)\n" | |
| 38 | +">>> snk(aubio.fvec(100), 100)\n" | |
| 31 | 39 | "\n" |
| 32 | −" >>> sink('/tmp/t.wav', samplerate = 32000, channels = 2)\n" | |
| 40 | +"Open a new sink at 48000Hz, stereo, write `1234` samples into it:\n" | |
| 33 | 41 | "\n" |
| 34 | −" Create a new sink at 32000Hz, 5 channels:\n" | |
| 42 | +">>> with aubio.sink('out.wav', samplerate=48000, channels=2) as src:\n" | |
| 43 | +"... snk(aubio.fvec(1024), 1024)\n" | |
| 44 | +"... snk(aubio.fvec(210), 210)\n" | |
| 45 | +"...\n" | |
| 35 | 46 | "\n" |
| 36 | −" >>> sink('/tmp/t.wav', channels = 5, samplerate = 32000)\n" | |
| 37 | −"\n" | |
| 38 | −" __call__(vec, write)\n" | |
| 39 | −" x(vec,write) <==> x.do(vec, write)\n" | |
| 40 | −"\n" | |
| 41 | −" Write vector to sink.\n" | |
| 42 | −"\n" | |
| 43 | −" See also\n" | |
| 44 | −" --------\n" | |
| 45 | −" aubio.sink.do\n" | |
| 47 | +"See also\n" | |
| 48 | +"--------\n" | |
| 49 | +"source: read audio samples from a file.\n" | |
| 46 | 50 | "\n"; |
| 47 | 51 | |
| 48 | 52 | static char Py_sink_do_doc[] = "" |
| 49 | −"x.do(vec, write) <==> x(vec, write)\n" | |
| 53 | +"do(vec, write)\n" | |
| 50 | 54 | "\n" |
| 51 | −"write monophonic vector to sink"; | |
| 55 | +"Write a single channel vector to sink.\n" | |
| 56 | +"\n" | |
| 57 | +"Parameters\n" | |
| 58 | +"----------\n" | |
| 59 | +"vec : fvec\n" | |
| 60 | +" input vector `(n,)` where `n >= 0`.\n" | |
| 61 | +"write : int\n" | |
| 62 | +" Number of samples to write.\n" | |
| 63 | +""; | |
| 52 | 64 | |
| 53 | 65 | static char Py_sink_do_multi_doc[] = "" |
| 54 | −"x.do_multi(mat, write)\n" | |
| 66 | +"do_multi(mat, write)\n" | |
| 67 | +"\n" | |
| 68 | +"Write a matrix containing vectors from multiple channels to sink.\n" | |
| 55 | 69 | "\n" |
| 56 | −"write polyphonic vector to sink"; | |
| 70 | +"Parameters\n" | |
| 71 | +"----------\n" | |
| 72 | +"mat : numpy.ndarray\n" | |
| 73 | +" input matrix of shape `(channels, n)`, where `n >= 0`.\n" | |
| 74 | +"write : int\n" | |
| 75 | +" Number of frames to write.\n" | |
| 76 | +""; | |
| 57 | 77 | |
| 58 | 78 | static char Py_sink_close_doc[] = "" |
| 59 | −"x.close()\n" | |
| 79 | +"close()\n" | |
| 60 | 80 | "\n" |
| 61 | −"close this sink now"; | |
| 81 | +"Close this sink now.\n" | |
| 82 | +"\n" | |
| 83 | +"By default, the sink will be closed before being deleted.\n" | |
| 84 | +"Explicitely closing a sink can be useful to control the number\n" | |
| 85 | +"of files simultaneously opened.\n" | |
| 86 | +""; | |
| 62 | 87 | |
| 63 | 88 | static PyObject * |
| 64 | 89 | Py_sink_new (PyTypeObject * pytype, PyObject * args, PyObject * kwds) |
| @@ -80,27 +105,20 @@ Py_sink_new (PyTypeObject * pytype, PyObject * args, PyObject * kwds) · 15 unmodified lines | ||
| 80 | 105 | return NULL; |
| 81 | 106 | } |
| 82 | 107 | |
| 83 | − self->uri = "none"; | |
| 108 | + self->uri = NULL; | |
| 84 | 109 | if (uri != NULL) { |
| 85 | − self->uri = uri; | |
| 110 | + self->uri = (char_t *)malloc(sizeof(char_t) * (strnlen(uri, PATH_MAX) + 1)); | |
| 111 | + strncpy(self->uri, uri, strnlen(uri, PATH_MAX) + 1); | |
| 86 | 112 | } |
| 87 | 113 | |
| 88 | 114 | self->samplerate = Py_aubio_default_samplerate; |
| 89 | − if ((sint_t)samplerate > 0) { | |
| 115 | + if (samplerate != 0) { | |
| 90 | 116 | self->samplerate = samplerate; |
| 91 | − } else if ((sint_t)samplerate < 0) { | |
| 92 | − PyErr_SetString (PyExc_ValueError, | |
| 93 | − "can not use negative value for samplerate"); | |
| 94 | − return NULL; | |
| 95 | 117 | } |
| 96 | 118 | |
| 97 | 119 | self->channels = 1; |
| 98 | − if ((sint_t)channels > 0) { | |
| 120 | + if (channels != 0) { | |
| 99 | 121 | self->channels = channels; |
| 100 | − } else if ((sint_t)channels < 0) { | |
| 101 | − PyErr_SetString (PyExc_ValueError, | |
| 102 | − "can not use negative or null value for channels"); | |
| 103 | − return NULL; | |
| 104 | 122 | } |
| 105 | 123 | |
| 106 | 124 | return (PyObject *) self; |
| @@ -109,17 +127,20 @@ Py_sink_new (PyTypeObject * pytype, PyObject * args, PyObject * kwds) · 2 unmodified lines | ||
| 109 | 127 | static int |
| 110 | 128 | Py_sink_init (Py_sink * self, PyObject * args, PyObject * kwds) |
| 111 | 129 | { |
| 112 | − if (self->channels == 1) { | |
| 113 | − self->o = new_aubio_sink ( self->uri, self->samplerate ); | |
| 114 | − } else { | |
| 115 | − self->o = new_aubio_sink ( self->uri, 0 ); | |
| 116 | − aubio_sink_preset_channels ( self->o, self->channels ); | |
| 117 | − aubio_sink_preset_samplerate ( self->o, self->samplerate ); | |
| 118 | − } | |
| 130 | + self->o = new_aubio_sink ( self->uri, 0 ); | |
| 119 | 131 | if (self->o == NULL) { |
| 120 | − PyErr_SetString (PyExc_RuntimeError, "error creating sink with this uri"); | |
| 132 | + // error string was set in new_aubio_sink | |
| 121 | 133 | return -1; |
| 122 | 134 | } |
| 135 | + if (aubio_sink_preset_channels(self->o, self->channels) != 0) { | |
| 136 | + // error string was set in aubio_sink_preset_channels | |
| 137 | + return -1; | |
| 138 | + } | |
| 139 | + if (aubio_sink_preset_samplerate(self->o, self->samplerate) != 0) { | |
| 140 | + // error string was set in aubio_sink_preset_samplerate | |
| 141 | + return -1; | |
| 142 | + } | |
| 143 | + | |
| 123 | 144 | self->samplerate = aubio_sink_get_samplerate ( self->o ); |
| 124 | 145 | self->channels = aubio_sink_get_channels ( self->o ); |
| 125 | 146 | |
| @@ -131,6 +152,9 @@ Py_sink_del (Py_sink *self, PyObject *unused) · 5 unmodified lines | ||
| 131 | 152 | { |
| 132 | 153 | del_aubio_sink(self->o); |
| 133 | 154 | free(self->mwrite_data.data); |
| 155 | + if (self->uri) { | |
| 156 | + free(self->uri); | |
| 157 | + } | |
| 134 | 158 | Py_TYPE(self)->tp_free((PyObject *) self); |
| 135 | 159 | } |
| 136 | 160 | |
| @@ -189,11 +213,11 @@ Py_sink_do_multi(Py_sink * self, PyObject * args) · 52 unmodified lines | ||
| 189 | 213 | |
| 190 | 214 | static PyMemberDef Py_sink_members[] = { |
| 191 | 215 | {"uri", T_STRING, offsetof (Py_sink, uri), READONLY, |
| 192 | − "path at which the sink was created"}, | |
| 216 | + "str (read-only): Path at which the sink was created."}, | |
| 193 | 217 | {"samplerate", T_INT, offsetof (Py_sink, samplerate), READONLY, |
| 194 | − "samplerate at which the sink was created"}, | |
| 218 | + "int (read-only): Samplerate at which the sink was created."}, | |
| 195 | 219 | {"channels", T_INT, offsetof (Py_sink, channels), READONLY, |
| 196 | − "number of channels with which the sink was created"}, | |
| 220 | + "int (read-only): Number of channels with which the sink was created."}, | |
| 197 | 221 | { NULL } // sentinel |
| 198 | 222 | }; |
| 199 | 223 | |
| @@ -204,10 +228,25 @@ Pyaubio_sink_close (Py_sink *self, PyObject *unused) · 4 unmodified lines | ||
| 204 | 228 | Py_RETURN_NONE; |
| 205 | 229 | } |
| 206 | 230 | |
| 231 | +static char Pyaubio_sink_enter_doc[] = ""; | |
| 232 | +static PyObject* Pyaubio_sink_enter(Py_sink *self, PyObject *unused) { | |
| 233 | + Py_INCREF(self); | |
| 234 | + return (PyObject*)self; | |
| 235 | +} | |
| 236 | + | |
| 237 | +static char Pyaubio_sink_exit_doc[] = ""; | |
| 238 | +static PyObject* Pyaubio_sink_exit(Py_sink *self, PyObject *unused) { | |
| 239 | + return Pyaubio_sink_close(self, unused); | |
| 240 | +} | |
| 241 | + | |
| 207 | 242 | static PyMethodDef Py_sink_methods[] = { |
| 208 | 243 | {"do", (PyCFunction) Py_sink_do, METH_VARARGS, Py_sink_do_doc}, |
| 209 | 244 | {"do_multi", (PyCFunction) Py_sink_do_multi, METH_VARARGS, Py_sink_do_multi_doc}, |
| 210 | 245 | {"close", (PyCFunction) Pyaubio_sink_close, METH_NOARGS, Py_sink_close_doc}, |
| 246 | + {"__enter__", (PyCFunction)Pyaubio_sink_enter, METH_NOARGS, | |
| 247 | + Pyaubio_sink_enter_doc}, | |
| 248 | + {"__exit__", (PyCFunction)Pyaubio_sink_exit, METH_VARARGS, | |
| 249 | + Pyaubio_sink_exit_doc}, | |
| 211 | 250 | {NULL} /* sentinel */ |
| 212 | 251 | }; |
| 213 | 252 | |
| @@ -16,68 +16,316 @@ typedef struct · 15 unmodified lines | ||
| 16 | 16 | } Py_source; |
| 17 | 17 | |
| 18 | 18 | static char Py_source_doc[] = "" |
| 19 | −" __new__(path, samplerate = 0, hop_size = 512, channels = 1)\n" | |
| 19 | +"source(path, samplerate=0, hop_size=512, channels=0)\n" | |
| 20 | 20 | "\n" |
| 21 | −" Create a new source, opening the given path for reading.\n" | |
| 21 | +"Read audio samples from a media file.\n" | |
| 22 | 22 | "\n" |
| 23 | −" Examples\n" | |
| 24 | −" --------\n" | |
| 23 | +"`source` open the file specified in `path` and creates a callable\n" | |
| 24 | +"returning `hop_size` new audio samples at each invocation.\n" | |
| 25 | 25 | "\n" |
| 26 | −" Create a new source, using the original samplerate, with hop_size = 512:\n" | |
| 26 | +"If `samplerate=0` (default), the original sampling rate of `path`\n" | |
| 27 | +"will be used. Otherwise, the output audio samples will be\n" | |
| 28 | +"resampled at the desired sampling-rate.\n" | |
| 27 | 29 | "\n" |
| 28 | −" >>> source('/tmp/t.wav')\n" | |
| 30 | +"If `channels=0` (default), the original number of channels\n" | |
| 31 | +"in `path` will be used. Otherwise, the output audio samples\n" | |
| 32 | +"will be down-mixed or up-mixed to the desired number of\n" | |
| 33 | +"channels.\n" | |
| 29 | 34 | "\n" |
| 30 | −" Create a new source, resampling the original to 8000Hz:\n" | |
| 35 | +"If `path` is a URL, a remote connection will be attempted to\n" | |
| 36 | +"open the resource and stream data from it.\n" | |
| 31 | 37 | "\n" |
| 32 | −" >>> source('/tmp/t.wav', samplerate = 8000)\n" | |
| 38 | +"The parameter `hop_size` determines how many samples should be\n" | |
| 39 | +"read at each consecutive calls.\n" | |
| 33 | 40 | "\n" |
| 34 | −" Create a new source, resampling it at 32000Hz, hop_size = 32:\n" | |
| 41 | +"Parameters\n" | |
| 42 | +"----------\n" | |
| 43 | +"path : str\n" | |
| 44 | +" pathname (or URL) of the file to be opened for reading\n" | |
| 45 | +"samplerate : int, optional\n" | |
| 46 | +" sampling rate of the file\n" | |
| 47 | +"hop_size : int, optional\n" | |
| 48 | +" number of samples to be read per iteration\n" | |
| 49 | +"channels : int, optional\n" | |
| 50 | +" number of channels of the file\n" | |
| 35 | 51 | "\n" |
| 36 | −" >>> source('/tmp/t.wav', samplerate = 32000, hop_size = 32)\n" | |
| 52 | +"Examples\n" | |
| 53 | +"--------\n" | |
| 54 | +"By default, when only `path` is given, the file will be opened\n" | |
| 55 | +"with its original sampling rate and channel:\n" | |
| 37 | 56 | "\n" |
| 38 | −" Create a new source, using its original samplerate:\n" | |
| 57 | +">>> src = aubio.source('stereo.wav')\n" | |
| 58 | +">>> src.uri, src.samplerate, src.channels, src.duration\n" | |
| 59 | +"('stereo.wav', 48000, 2, 86833)\n" | |
| 39 | 60 | "\n" |
| 40 | −" >>> source('/tmp/t.wav', samplerate = 0)\n" | |
| 61 | +"A typical loop to read all samples from a local file could\n" | |
| 62 | +"look like this:\n" | |
| 41 | 63 | "\n" |
| 42 | −" __call__()\n" | |
| 43 | −" vec, read = x() <==> vec, read = x.do()\n" | |
| 64 | +">>> src = aubio.source('stereo.wav')\n" | |
| 65 | +">>> total_read = 0\n" | |
| 66 | +">>> while True:\n" | |
| 67 | +"... samples, read = src()\n" | |
| 68 | +"... # do something with samples\n" | |
| 69 | +"... total_read += read\n" | |
| 70 | +"... if read < src.hop_size:\n" | |
| 71 | +"... break\n" | |
| 72 | +"...\n" | |
| 44 | 73 | "\n" |
| 45 | −" Read vector from source.\n" | |
| 74 | +"In a more Pythonic way, it can also look like this:\n" | |
| 46 | 75 | "\n" |
| 47 | −" See also\n" | |
| 48 | −" --------\n" | |
| 49 | −" aubio.source.do\n" | |
| 50 | −"\n"; | |
| 76 | +">>> total_read = 0\n" | |
| 77 | +">>> with aubio.source('stereo.wav') as src:\n" | |
| 78 | +"... for frames in src:\n" | |
| 79 | +"... total_read += samples.shape[-1]\n" | |
| 80 | +"...\n" | |
| 81 | +"\n" | |
| 82 | +".. rubric:: Basic interface\n" | |
| 83 | +"\n" | |
| 84 | +"`source` is a **callable**; its :meth:`__call__` method\n" | |
| 85 | +"returns a tuple containing:\n" | |
| 86 | +"\n" | |
| 87 | +"- a vector of shape `(hop_size,)`, filled with the `read` next\n" | |
| 88 | +" samples available, zero-padded if `read < hop_size`\n" | |
| 89 | +"- `read`, an integer indicating the number of samples read\n" | |
| 90 | +"\n" | |
| 91 | +"To read the first `hop_size` samples from the source, simply call\n" | |
| 92 | +"the instance itself, with no argument:\n" | |
| 93 | +"\n" | |
| 94 | +">>> src = aubio.source('song.ogg')\n" | |
| 95 | +">>> samples, read = src()\n" | |
| 96 | +">>> samples.shape, read, src.hop_size\n" | |
| 97 | +"((512,), 512, 512)\n" | |
| 98 | +"\n" | |
| 99 | +"The first call returned the slice of samples `[0 : hop_size]`.\n" | |
| 100 | +"The next call will return samples `[hop_size: 2*hop_size]`.\n" | |
| 101 | +"\n" | |
| 102 | +"After several invocations of :meth:`__call__`, when reaching the end\n" | |
| 103 | +"of the opened stream, `read` might become less than `hop_size`:\n" | |
| 104 | +"\n" | |
| 105 | +">>> samples, read = src()\n" | |
| 106 | +">>> samples.shape, read\n" | |
| 107 | +"((512,), 354)\n" | |
| 108 | +"\n" | |
| 109 | +"The end of the vector `samples` is filled with zeros.\n" | |
| 110 | +"\n" | |
| 111 | +"After the end of the stream, `read` will be `0` since no more\n" | |
| 112 | +"samples are available:\n" | |
| 113 | +"\n" | |
| 114 | +">>> samples, read = src()\n" | |
| 115 | +">>> samples.shape, read\n" | |
| 116 | +"((512,), 0)\n" | |
| 117 | +"\n" | |
| 118 | +"**Note**: when the source has more than one channels, they\n" | |
| 119 | +"are be down-mixed to mono when invoking :meth:`__call__`.\n" | |
| 120 | +"To read from each individual channel, see :meth:`__next__`.\n" | |
| 121 | +"\n" | |
| 122 | +".. rubric:: ``for`` statements\n" | |
| 123 | +"\n" | |
| 124 | +"The `source` objects are **iterables**. This allows using them\n" | |
| 125 | +"directly in a ``for`` loop, which calls :meth:`__next__` until\n" | |
| 126 | +"the end of the stream is reached:\n" | |
| 127 | +"\n" | |
| 128 | +">>> src = aubio.source('stereo.wav')\n" | |
| 129 | +">>> for frames in src:\n" | |
| 130 | +">>> print (frames.shape)\n" | |
| 131 | +"...\n" | |
| 132 | +"(2, 512)\n" | |
| 133 | +"(2, 512)\n" | |
| 134 | +"(2, 230)\n" | |
| 135 | +"\n" | |
| 136 | +"**Note**: When `next(self)` is called on a source with multiple\n" | |
| 137 | +"channels, an array of shape `(channels, read)` is returned,\n" | |
| 138 | +"unlike with :meth:`__call__` which always returns the down-mixed\n" | |
| 139 | +"channels.\n" | |
| 140 | +"\n" | |
| 141 | +"If the file is opened with a single channel, `next(self)` returns\n" | |
| 142 | +"an array of shape `(read,)`:\n" | |
| 143 | +"\n" | |
| 144 | +">>> src = aubio.source('stereo.wav', channels=1)\n" | |
| 145 | +">>> next(src).shape\n" | |
| 146 | +"(512,)\n" | |
| 147 | +"\n" | |
| 148 | +".. rubric:: ``with`` statements\n" | |
| 149 | +"\n" | |
| 150 | +"The `source` objects are **context managers**, which allows using\n" | |
| 151 | +"them in ``with`` statements:\n" | |
| 152 | +"\n" | |
| 153 | +">>> with aubio.source('audiotrack.wav') as source:\n" | |
| 154 | +"... n_frames=0\n" | |
| 155 | +"... for samples in source:\n" | |
| 156 | +"... n_frames += len(samples)\n" | |
| 157 | +"... print('read', n_frames, 'samples in', samples.shape[0], 'channels',\n" | |
| 158 | +"... 'from file \"%%s\"' %% source.uri)\n" | |
| 159 | +"...\n" | |
| 160 | +"read 239334 samples in 2 channels from file \"audiotrack.wav\"\n" | |
| 161 | +"\n" | |
| 162 | +"The file will be closed before exiting the statement.\n" | |
| 163 | +"\n" | |
| 164 | +"See also the methods implementing the context manager,\n" | |
| 165 | +":meth:`__enter__` and :meth:`__exit__`.\n" | |
| 166 | +"\n" | |
| 167 | +".. rubric:: Seeking and closing\n" | |
| 168 | +"\n" | |
| 169 | +"At any time, :meth:`seek` can be used to move to any position in\n" | |
| 170 | +"the file. For instance, to rewind to the start of the stream:\n" | |
| 171 | +"\n" | |
| 172 | +">>> src.seek(0)\n" | |
| 173 | +"\n" | |
| 174 | +"The opened file will be automatically closed when the object falls\n" | |
| 175 | +"out of scope and is scheduled for garbage collection.\n" | |
| 176 | +"\n" | |
| 177 | +"In some cases, it is useful to manually :meth:`close` a given source,\n" | |
| 178 | +"for instance to limit the number of simultaneously opened files:\n" | |
| 179 | +"\n" | |
| 180 | +">>> src.close()\n" | |
| 181 | +"\n" | |
| 182 | +".. rubric:: Input formats\n" | |
| 183 | +"\n" | |
| 184 | +"Depending on how aubio was compiled, :class:`source` may or may not\n" | |
| 185 | +"open certain **files format**. Below are some examples that assume\n" | |
| 186 | +"support for compressed files and remote urls was compiled in:\n" | |
| 187 | +"\n" | |
| 188 | +"- open a local file using its original sampling rate and channels,\n" | |
| 189 | +" and with the default hop size:\n" | |
| 190 | +"\n" | |
| 191 | +">>> s = aubio.source('sample.wav')\n" | |
| 192 | +">>> s.uri, s.samplerate, s.channels, s.hop_size\n" | |
| 193 | +"('sample.wav', 44100, 2, 512)\n" | |
| 194 | +"\n" | |
| 195 | +"- open a local compressed audio file, resampling to 32000Hz if needed:\n" | |
| 196 | +"\n" | |
| 197 | +">>> s = aubio.source('song.mp3', samplerate=32000)\n" | |
| 198 | +">>> s.uri, s.samplerate, s.channels, s.hop_size\n" | |
| 199 | +"('song.mp3', 32000, 2, 512)\n" | |
| 200 | +"\n" | |
| 201 | +"- open a local video file, down-mixing and resampling it to 16kHz:\n" | |
| 202 | +"\n" | |
| 203 | +">>> s = aubio.source('movie.mp4', samplerate=16000, channels=1)\n" | |
| 204 | +">>> s.uri, s.samplerate, s.channels, s.hop_size\n" | |
| 205 | +"('movie.mp4', 16000, 1, 512)\n" | |
| 206 | +"\n" | |
| 207 | +"- open a remote resource, with hop_size = 1024:\n" | |
| 208 | +"\n" | |
| 209 | +">>> s = aubio.source('https://aubio.org/drum.ogg', hop_size=1024)\n" | |
| 210 | +">>> s.uri, s.samplerate, s.channels, s.hop_size\n" | |
| 211 | +"('https://aubio.org/drum.ogg', 48000, 2, 1024)\n" | |
| 212 | +"\n" | |
| 213 | +"See Also\n" | |
| 214 | +"--------\n" | |
| 215 | +"sink: write audio samples to a file.\n" | |
| 216 | +""; | |
| 51 | 217 | |
| 52 | 218 | static char Py_source_get_samplerate_doc[] = "" |
| 53 | −"x.get_samplerate() -> source samplerate\n" | |
| 219 | +"get_samplerate()\n" | |
| 220 | +"\n" | |
| 221 | +"Get sampling rate of source.\n" | |
| 54 | 222 | "\n" |
| 55 | −"Get samplerate of source."; | |
| 223 | +"Returns\n" | |
| 224 | +"-------\n" | |
| 225 | +"int\n" | |
| 226 | +" Sampling rate, in Hz.\n" | |
| 227 | +""; | |
| 56 | 228 | |
| 57 | 229 | static char Py_source_get_channels_doc[] = "" |
| 58 | −"x.get_channels() -> number of channels\n" | |
| 230 | +"get_channels()\n" | |
| 59 | 231 | "\n" |
| 60 | −"Get number of channels in source."; | |
| 232 | +"Get number of channels in source.\n" | |
| 233 | +"\n" | |
| 234 | +"Returns\n" | |
| 235 | +"-------\n" | |
| 236 | +"int\n" | |
| 237 | +" Number of channels.\n" | |
| 238 | +""; | |
| 61 | 239 | |
| 62 | 240 | static char Py_source_do_doc[] = "" |
| 63 | −"vec, read = x.do() <==> vec, read = x()\n" | |
| 241 | +"source.do()\n" | |
| 242 | +"\n" | |
| 243 | +"Read vector of audio samples.\n" | |
| 244 | +"\n" | |
| 245 | +"If the audio stream in the source has more than one channel,\n" | |
| 246 | +"the channels will be down-mixed.\n" | |
| 247 | +"\n" | |
| 248 | +"Returns\n" | |
| 249 | +"-------\n" | |
| 250 | +"samples : numpy.ndarray\n" | |
| 251 | +" `fvec` of size `hop_size` containing the new samples.\n" | |
| 252 | +"read : int\n" | |
| 253 | +" Number of samples read from the source, equals to `hop_size`\n" | |
| 254 | +" before the end-of-file is reached, less when it is reached,\n" | |
| 255 | +" and `0` after.\n" | |
| 64 | 256 | "\n" |
| 65 | −"Read monophonic vector from source."; | |
| 257 | +"See Also\n" | |
| 258 | +"--------\n" | |
| 259 | +"do_multi\n" | |
| 260 | +"\n" | |
| 261 | +"Examples\n" | |
| 262 | +"--------\n" | |
| 263 | +">>> src = aubio.source('sample.wav', hop_size=1024)\n" | |
| 264 | +">>> src.do()\n" | |
| 265 | +"(array([-0.00123001, -0.00036685, 0.00097106, ..., -0.2031033 ,\n" | |
| 266 | +" -0.2025854 , -0.20221856], dtype=" AUBIO_NPY_SMPL_STR "), 1024)\n" | |
| 267 | +""; | |
| 66 | 268 | |
| 67 | 269 | static char Py_source_do_multi_doc[] = "" |
| 68 | −"mat, read = x.do_multi()\n" | |
| 270 | +"do_multi()\n" | |
| 271 | +"\n" | |
| 272 | +"Read multiple channels of audio samples.\n" | |
| 273 | +"\n" | |
| 274 | +"If the source was opened with the same number of channels\n" | |
| 275 | +"found in the stream, each channel will be read individually.\n" | |
| 276 | +"\n" | |
| 277 | +"If the source was opened with less channels than the number\n" | |
| 278 | +"of channels in the stream, only the first channels will be read.\n" | |
| 279 | +"\n" | |
| 280 | +"If the source was opened with more channels than the number\n" | |
| 281 | +"of channel in the original stream, the first channels will\n" | |
| 282 | +"be duplicated on the additional output channel.\n" | |
| 69 | 283 | "\n" |
| 70 | −"Read polyphonic vector from source."; | |
| 284 | +"Returns\n" | |
| 285 | +"-------\n" | |
| 286 | +"samples : numpy.ndarray\n" | |
| 287 | +" NumPy array of shape `(hop_size, channels)` containing the new\n" | |
| 288 | +" audio samples.\n" | |
| 289 | +"read : int\n" | |
| 290 | +" Number of samples read from the source, equals to `hop_size`\n" | |
| 291 | +" before the end-of-file is reached, less when it is reached,\n" | |
| 292 | +" and `0` after.\n" | |
| 293 | +"\n" | |
| 294 | +"See Also\n" | |
| 295 | +"--------\n" | |
| 296 | +"do\n" | |
| 297 | +"\n" | |
| 298 | +"Examples\n" | |
| 299 | +"--------\n" | |
| 300 | +">>> src = aubio.source('sample.wav')\n" | |
| 301 | +">>> src.do_multi()\n" | |
| 302 | +"(array([[ 0.00668335, 0.0067749 , 0.00714111, ..., -0.05737305,\n" | |
| 303 | +" -0.05856323, -0.06018066],\n" | |
| 304 | +" [-0.00842285, -0.0072937 , -0.00576782, ..., -0.09405518,\n" | |
| 305 | +" -0.09558105, -0.09725952]], dtype=" AUBIO_NPY_SMPL_STR "), 512)\n" | |
| 306 | +""; | |
| 71 | 307 | |
| 72 | 308 | static char Py_source_close_doc[] = "" |
| 73 | −"x.close()\n" | |
| 309 | +"close()\n" | |
| 74 | 310 | "\n" |
| 75 | −"Close this source now."; | |
| 311 | +"Close this source now.\n" | |
| 312 | +"\n" | |
| 313 | +".. note:: Closing twice a source will **not** raise any exception.\n" | |
| 314 | +""; | |
| 76 | 315 | |
| 77 | 316 | static char Py_source_seek_doc[] = "" |
| 78 | −"x.seek(position)\n" | |
| 317 | +"seek(position)\n" | |
| 318 | +"\n" | |
| 319 | +"Seek to position in file.\n" | |
| 79 | 320 | "\n" |
| 80 | −"Seek to resampled frame position."; | |
| 321 | +"If the source was not opened with its original sampling-rate,\n" | |
| 322 | +"`position` corresponds to the position in the re-sampled file.\n" | |
| 323 | +"\n" | |
| 324 | +"Parameters\n" | |
| 325 | +"----------\n" | |
| 326 | +"position : str\n" | |
| 327 | +" position to seek to, in samples\n" | |
| 328 | +""; | |
| 81 | 329 | |
| 82 | 330 | static PyObject * |
| 83 | 331 | Py_source_new (PyTypeObject * pytype, PyObject * args, PyObject * kwds) |
| @@ -100,9 +348,10 @@ Py_source_new (PyTypeObject * pytype, PyObject * args, PyObject * kwds) · 16 unmodified lines | ||
| 100 | 348 | return NULL; |
| 101 | 349 | } |
| 102 | 350 | |
| 103 | − self->uri = "none"; | |
| 351 | + self->uri = NULL; | |
| 104 | 352 | if (uri != NULL) { |
| 105 | − self->uri = uri; | |
| 353 | + self->uri = (char_t *)malloc(sizeof(char_t) * (strnlen(uri, PATH_MAX) + 1)); | |
| 354 | + strncpy(self->uri, uri, strnlen(uri, PATH_MAX) + 1); | |
| 106 | 355 | } |
| 107 | 356 | |
| 108 | 357 | self->samplerate = 0; |
| @@ -163,6 +412,9 @@ Py_source_del (Py_source *self, PyObject *unused) · 54 unmodified lines | ||
| 163 | 412 | del_aubio_source(self->o); |
| 164 | 413 | free(self->c_mread_to.data); |
| 165 | 414 | } |
| 415 | + if (self->uri) { | |
| 416 | + free(self->uri); | |
| 417 | + } | |
| 166 | 418 | Py_XDECREF(self->read_to); |
| 167 | 419 | Py_XDECREF(self->mread_to); |
| 168 | 420 | Py_TYPE(self)->tp_free((PyObject *) self); |
| @@ -213,15 +465,29 @@ Py_source_do_multi(Py_source * self, PyObject * args) · 44 unmodified lines | ||
| 213 | 465 | |
| 214 | 466 | static PyMemberDef Py_source_members[] = { |
| 215 | 467 | {"uri", T_STRING, offsetof (Py_source, uri), READONLY, |
| 216 | − "path at which the source was created"}, | |
| 468 | + "str (read-only): pathname or URL"}, | |
| 217 | 469 | {"samplerate", T_INT, offsetof (Py_source, samplerate), READONLY, |
| 218 | − "samplerate at which the source is viewed"}, | |
| 470 | + "int (read-only): sampling rate"}, | |
| 219 | 471 | {"channels", T_INT, offsetof (Py_source, channels), READONLY, |
| 220 | − "number of channels found in the source"}, | |
| 472 | + "int (read-only): number of channels"}, | |
| 221 | 473 | {"hop_size", T_INT, offsetof (Py_source, hop_size), READONLY, |
| 222 | − "number of consecutive frames that will be read at each do or do_multi call"}, | |
| 474 | + "int (read-only): number of samples read per iteration"}, | |
| 223 | 475 | {"duration", T_INT, offsetof (Py_source, duration), READONLY, |
| 224 | − "total number of frames in the source (estimated)"}, | |
| 476 | + "int (read-only): total number of frames in the source\n" | |
| 477 | + "\n" | |
| 478 | + "Can be estimated, for instance if the opened stream is\n" | |
| 479 | + "a compressed media or a remote resource.\n" | |
| 480 | + "\n" | |
| 481 | + "Example\n" | |
| 482 | + "-------\n" | |
| 483 | + ">>> n = 0\n" | |
| 484 | + ">>> src = aubio.source('track1.mp3')\n" | |
| 485 | + ">>> for samples in src:\n" | |
| 486 | + "... n += samples.shape[-1]\n" | |
| 487 | + "...\n" | |
| 488 | + ">>> n, src.duration\n" | |
| 489 | + "(9638784, 9616561)\n" | |
| 490 | + ""}, | |
| 225 | 491 | { NULL } // sentinel |
| 226 | 492 | }; |
| 227 | 493 | |
| @@ -242,7 +508,7 @@ Pyaubio_source_get_channels (Py_source *self, PyObject *unused) · 14 unmodified lines | ||
| 242 | 508 | static PyObject * |
| 243 | 509 | Pyaubio_source_close (Py_source *self, PyObject *unused) |
| 244 | 510 | { |
| 245 | − aubio_source_close (self->o); | |
| 511 | + if (aubio_source_close(self->o) != 0) return NULL; | |
| 246 | 512 | Py_RETURN_NONE; |
| 247 | 513 | } |
| 248 | 514 | |
| @@ -272,6 +538,65 @@ Pyaubio_source_seek (Py_source *self, PyObject *args) · 23 unmodified lines | ||
| 272 | 538 | Py_RETURN_NONE; |
| 273 | 539 | } |
| 274 | 540 | |
| 541 | +static char Pyaubio_source_enter_doc[] = ""; | |
| 542 | +static PyObject* Pyaubio_source_enter(Py_source *self, PyObject *unused) { | |
| 543 | + Py_INCREF(self); | |
| 544 | + return (PyObject*)self; | |
| 545 | +} | |
| 546 | + | |
| 547 | +static char Pyaubio_source_exit_doc[] = ""; | |
| 548 | +static PyObject* Pyaubio_source_exit(Py_source *self, PyObject *unused) { | |
| 549 | + return Pyaubio_source_close(self, unused); | |
| 550 | +} | |
| 551 | + | |
| 552 | +static PyObject* Pyaubio_source_iter(PyObject *self) { | |
| 553 | + Py_INCREF(self); | |
| 554 | + return (PyObject*)self; | |
| 555 | +} | |
| 556 | + | |
| 557 | +static PyObject* Pyaubio_source_iter_next(Py_source *self) { | |
| 558 | + PyObject *done, *size; | |
| 559 | + if (self->channels == 1) { | |
| 560 | + done = Py_source_do(self, NULL); | |
| 561 | + } else { | |
| 562 | + done = Py_source_do_multi(self, NULL); | |
| 563 | + } | |
| 564 | + if (!PyTuple_Check(done)) { | |
| 565 | + PyErr_Format(PyExc_ValueError, | |
| 566 | + "error when reading source: not opened?"); | |
| 567 | + return NULL; | |
| 568 | + } | |
| 569 | + size = PyTuple_GetItem(done, 1); | |
| 570 | + if (size != NULL && PyLong_Check(size)) { | |
| 571 | + if (PyLong_AsLong(size) == (long)self->hop_size) { | |
| 572 | + PyObject *vec = PyTuple_GetItem(done, 0); | |
| 573 | + return vec; | |
| 574 | + } else if (PyLong_AsLong(size) > 0) { | |
| 575 | + // short read, return a shorter array | |
| 576 | + PyArrayObject *shortread = (PyArrayObject*)PyTuple_GetItem(done, 0); | |
| 577 | + PyArray_Dims newdims; | |
| 578 | + PyObject *reshaped; | |
| 579 | + newdims.len = PyArray_NDIM(shortread); | |
| 580 | + newdims.ptr = PyArray_DIMS(shortread); | |
| 581 | + // mono or multiple channels? | |
| 582 | + if (newdims.len == 1) { | |
| 583 | + newdims.ptr[0] = PyLong_AsLong(size); | |
| 584 | + } else { | |
| 585 | + newdims.ptr[1] = PyLong_AsLong(size); | |
| 586 | + } | |
| 587 | + reshaped = PyArray_Newshape(shortread, &newdims, NPY_CORDER); | |
| 588 | + Py_DECREF(shortread); | |
| 589 | + return reshaped; | |
| 590 | + } else { | |
| 591 | + PyErr_SetNone(PyExc_StopIteration); | |
| 592 | + return NULL; | |
| 593 | + } | |
| 594 | + } else { | |
| 595 | + PyErr_SetNone(PyExc_StopIteration); | |
| 596 | + return NULL; | |
| 597 | + } | |
| 598 | +} | |
| 599 | + | |
| 275 | 600 | static PyMethodDef Py_source_methods[] = { |
| 276 | 601 | {"get_samplerate", (PyCFunction) Pyaubio_source_get_samplerate, |
| 277 | 602 | METH_NOARGS, Py_source_get_samplerate_doc}, |
| @@ -285,6 +610,10 @@ static PyMethodDef Py_source_methods[] = { · 7 unmodified lines | ||
| 285 | 610 | METH_NOARGS, Py_source_close_doc}, |
| 286 | 611 | {"seek", (PyCFunction) Pyaubio_source_seek, |
| 287 | 612 | METH_VARARGS, Py_source_seek_doc}, |
| 613 | + {"__enter__", (PyCFunction)Pyaubio_source_enter, METH_NOARGS, | |
| 614 | + Pyaubio_source_enter_doc}, | |
| 615 | + {"__exit__", (PyCFunction)Pyaubio_source_exit, METH_VARARGS, | |
| 616 | + Pyaubio_source_exit_doc}, | |
| 288 | 617 | {NULL} /* sentinel */ |
| 289 | 618 | }; |
| 290 | 619 | |
| @@ -314,8 +643,8 @@ PyTypeObject Py_sourceType = { · 23 unmodified lines | ||
| 314 | 643 | 0, |
| 315 | 644 | 0, |
| 316 | 645 | 0, |
| 317 | − 0, | |
| 318 | − 0, | |
| 646 | + Pyaubio_source_iter, | |
| 647 | + (unaryfunc)Pyaubio_source_iter_next, | |
| 319 | 648 | Py_source_methods, |
| 320 | 649 | Py_source_members, |
| 321 | 650 | 0, |
| @@ -58,7 +58,22 @@ static char Py_aubio_unary_types[] = { · 57 unmodified lines | ||
| 58 | 58 | //NPY_OBJECT, NPY_OBJECT, |
| 59 | 59 | }; |
| 60 | 60 | |
| 61 | −static char Py_unwrap2pi_doc[] = "map angle to unit circle [-pi, pi["; | |
| 61 | +// Note: these docstrings should *not* include the function signatures | |
| 62 | + | |
| 63 | +static char Py_unwrap2pi_doc[] = "" | |
| 64 | +"\n" | |
| 65 | +"Map angle to unit circle :math:`[-\\pi, \\pi[`.\n" | |
| 66 | +"\n" | |
| 67 | +"Parameters\n" | |
| 68 | +"----------\n" | |
| 69 | +"x : numpy.ndarray\n" | |
| 70 | +" input array\n" | |
| 71 | +"\n" | |
| 72 | +"Returns\n" | |
| 73 | +"-------\n" | |
| 74 | +"numpy.ndarray\n" | |
| 75 | +" values clamped to the unit circle :math:`[-\\pi, \\pi[`\n" | |
| 76 | +""; | |
| 62 | 77 | |
| 63 | 78 | static void* Py_unwrap2pi_data[] = { |
| 64 | 79 | (void *)aubio_unwrap2pi, |
| @@ -67,14 +82,40 @@ static void* Py_unwrap2pi_data[] = { · 2 unmodified lines | ||
| 67 | 82 | //(void *)unwrap2pio, |
| 68 | 83 | }; |
| 69 | 84 | |
| 70 | −static char Py_freqtomidi_doc[] = "convert frequency to midi"; | |
| 85 | +static char Py_freqtomidi_doc[] = "" | |
| 86 | +"\n" | |
| 87 | +"Convert frequency `[0; 23000[` to midi `[0; 128[`.\n" | |
| 88 | +"\n" | |
| 89 | +"Parameters\n" | |
| 90 | +"----------\n" | |
| 91 | +"x : numpy.ndarray\n" | |
| 92 | +" Array of frequencies, in Hz.\n" | |
| 93 | +"\n" | |
| 94 | +"Returns\n" | |
| 95 | +"-------\n" | |
| 96 | +"numpy.ndarray\n" | |
| 97 | +" Converted frequencies, in midi note.\n" | |
| 98 | +""; | |
| 71 | 99 | |
| 72 | 100 | static void* Py_freqtomidi_data[] = { |
| 73 | 101 | (void *)aubio_freqtomidi, |
| 74 | 102 | (void *)aubio_freqtomidi, |
| 75 | 103 | }; |
| 76 | 104 | |
| 77 | −static char Py_miditofreq_doc[] = "convert midi to frequency"; | |
| 105 | +static char Py_miditofreq_doc[] = "" | |
| 106 | +"\n" | |
| 107 | +"Convert midi `[0; 128[` to frequency `[0, 23000]`.\n" | |
| 108 | +"\n" | |
| 109 | +"Parameters\n" | |
| 110 | +"----------\n" | |
| 111 | +"x : numpy.ndarray\n" | |
| 112 | +" Array of frequencies, in midi note.\n" | |
| 113 | +"\n" | |
| 114 | +"Returns\n" | |
| 115 | +"-------\n" | |
| 116 | +"numpy.ndarray\n" | |
| 117 | +" Converted frequencies, in Hz\n" | |
| 118 | +""; | |
| 78 | 119 | |
| 79 | 120 | static void* Py_miditofreq_data[] = { |
| 80 | 121 | (void *)aubio_miditofreq, |
Empty file deleted.
| @@ -1,18 +1,85 @@ | ||
| 1 | 1 | #! /usr/bin/env python |
| 2 | +# -*- coding: utf8 -*- | |
| 3 | + | |
| 4 | +""" | |
| 5 | +aubio | |
| 6 | +===== | |
| 7 | + | |
| 8 | +Provides a number of classes and functions for music and audio signal | |
| 9 | +analysis. | |
| 10 | + | |
| 11 | +How to use the documentation | |
| 12 | +---------------------------- | |
| 13 | + | |
| 14 | +Documentation of the python module is available as docstrings provided | |
| 15 | +within the code, and a reference guide available online from `the | |
| 16 | +aubio homepage <https://aubio.org/documentation>`_. | |
| 17 | + | |
| 18 | +The docstrings examples are written assuming `aubio` and `numpy` have been | |
| 19 | +imported with: | |
| 20 | + | |
| 21 | +>>> import aubio | |
| 22 | +>>> import numpy as np | |
| 23 | +""" | |
| 2 | 24 | |
| 3 | 25 | import numpy |
| 4 | −from ._aubio import * | |
| 26 | +from ._aubio import __version__ as version | |
| 5 | 27 | from ._aubio import float_type |
| 28 | +from ._aubio import * | |
| 6 | 29 | from .midiconv import * |
| 7 | 30 | from .slicing import * |
| 8 | 31 | |
| 32 | + | |
| 9 | 33 | class fvec(numpy.ndarray): |
| 10 | − """a numpy vector holding audio samples""" | |
| 34 | + """fvec(input_arg=1024) | |
| 35 | + A vector holding float samples. | |
| 36 | + | |
| 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')) | |
| 11 | 58 | |
| 12 | − def __new__(cls, input_arg=1024, **kwargs): | |
| 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): | |
| 13 | 75 | if isinstance(input_arg, int): |
| 14 | 76 | if input_arg == 0: |
| 15 | 77 | 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') | |
| 17 | 79 | 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 | |
| @@ -0,0 +1,623 @@ | ||
| 1 | +#! /usr/bin/env python | |
| 2 | +# -*- coding: utf-8 -*- | |
| 3 | + | |
| 4 | +"""aubio command line tool | |
| 5 | + | |
| 6 | +This file was written by Paul Brossier <piem@aubio.org> and is released under | |
| 7 | +the GNU/GPL v3. | |
| 8 | + | |
| 9 | +Note: this script is mostly about parsing command line arguments. For more | |
| 10 | +readable code examples, check out the `python/demos` folder.""" | |
| 11 | + | |
| 12 | +import sys | |
| 13 | +import argparse | |
| 14 | +import warnings | |
| 15 | +import aubio | |
| 16 | + | |
| 17 | +def aubio_parser(): | |
| 18 | + epilog = 'use "%(prog)s <command> --help" for more info about each command' | |
| 19 | + parser = argparse.ArgumentParser(epilog=epilog) | |
| 20 | + parser.add_argument('-V', '--version', help="show version", | |
| 21 | + action="store_true", dest="show_version") | |
| 22 | + | |
| 23 | + subparsers = parser.add_subparsers(title='commands', dest='command', | |
| 24 | + parser_class= AubioArgumentParser, | |
| 25 | + metavar="") | |
| 26 | + | |
| 27 | + parser_add_subcommand_help(subparsers) | |
| 28 | + | |
| 29 | + parser_add_subcommand_onset(subparsers) | |
| 30 | + parser_add_subcommand_pitch(subparsers) | |
| 31 | + parser_add_subcommand_beat(subparsers) | |
| 32 | + parser_add_subcommand_tempo(subparsers) | |
| 33 | + parser_add_subcommand_notes(subparsers) | |
| 34 | + parser_add_subcommand_mfcc(subparsers) | |
| 35 | + parser_add_subcommand_melbands(subparsers) | |
| 36 | + parser_add_subcommand_quiet(subparsers) | |
| 37 | + parser_add_subcommand_cut(subparsers) | |
| 38 | + | |
| 39 | + return parser | |
| 40 | + | |
| 41 | +def parser_add_subcommand_help(subparsers): | |
| 42 | + # global help subcommand | |
| 43 | + subparsers.add_parser('help', | |
| 44 | + help='show help message', | |
| 45 | + formatter_class = argparse.ArgumentDefaultsHelpFormatter) | |
| 46 | + | |
| 47 | +def parser_add_subcommand_onset(subparsers): | |
| 48 | + # onset subcommand | |
| 49 | + subparser = subparsers.add_parser('onset', | |
| 50 | + help='estimate time of onsets (beginning of sound event)', | |
| 51 | + formatter_class = argparse.ArgumentDefaultsHelpFormatter) | |
| 52 | + subparser.add_input() | |
| 53 | + subparser.add_buf_hop_size() | |
| 54 | + helpstr = "onset novelty function" | |
| 55 | + helpstr += " <default|energy|hfc|complex|phase|specdiff|kl|mkl|specflux>" | |
| 56 | + subparser.add_method(helpstr=helpstr) | |
| 57 | + subparser.add_threshold() | |
| 58 | + subparser.add_silence() | |
| 59 | + subparser.add_minioi() | |
| 60 | + subparser.add_time_format() | |
| 61 | + subparser.add_verbose_help() | |
| 62 | + subparser.set_defaults(process=process_onset) | |
| 63 | + | |
| 64 | +def parser_add_subcommand_pitch(subparsers): | |
| 65 | + # pitch subcommand | |
| 66 | + subparser = subparsers.add_parser('pitch', | |
| 67 | + help='estimate fundamental frequency (monophonic)') | |
| 68 | + subparser.add_input() | |
| 69 | + subparser.add_buf_hop_size(buf_size=2048) | |
| 70 | + helpstr = "pitch detection method <default|yinfft|yin|mcomb|fcomb|schmitt>" | |
| 71 | + subparser.add_method(helpstr=helpstr) | |
| 72 | + subparser.add_threshold() | |
| 73 | + subparser.add_pitch_unit() | |
| 74 | + subparser.add_silence() | |
| 75 | + subparser.add_time_format() | |
| 76 | + subparser.add_verbose_help() | |
| 77 | + subparser.set_defaults(process=process_pitch) | |
| 78 | + | |
| 79 | +def parser_add_subcommand_beat(subparsers): | |
| 80 | + # beat subcommand | |
| 81 | + subparser = subparsers.add_parser('beat', | |
| 82 | + help='estimate location of beats') | |
| 83 | + subparser.add_input() | |
| 84 | + subparser.add_buf_hop_size(buf_size=1024, hop_size=512) | |
| 85 | + subparser.add_time_format() | |
| 86 | + subparser.add_verbose_help() | |
| 87 | + subparser.set_defaults(process=process_beat) | |
| 88 | + | |
| 89 | +def parser_add_subcommand_tempo(subparsers): | |
| 90 | + # tempo subcommand | |
| 91 | + subparser = subparsers.add_parser('tempo', | |
| 92 | + help='estimate overall tempo in bpm') | |
| 93 | + subparser.add_input() | |
| 94 | + subparser.add_buf_hop_size(buf_size=1024, hop_size=512) | |
| 95 | + subparser.add_time_format() | |
| 96 | + subparser.add_verbose_help() | |
| 97 | + subparser.set_defaults(process=process_tempo) | |
| 98 | + | |
| 99 | +def parser_add_subcommand_notes(subparsers): | |
| 100 | + # notes subcommand | |
| 101 | + subparser = subparsers.add_parser('notes', | |
| 102 | + help='estimate midi-like notes (monophonic)') | |
| 103 | + subparser.add_input() | |
| 104 | + subparser.add_buf_hop_size() | |
| 105 | + subparser.add_silence() | |
| 106 | + subparser.add_release_drop() | |
| 107 | + subparser.add_time_format() | |
| 108 | + subparser.add_verbose_help() | |
| 109 | + subparser.set_defaults(process=process_notes) | |
| 110 | + | |
| 111 | +def parser_add_subcommand_mfcc(subparsers): | |
| 112 | + # mfcc subcommand | |
| 113 | + subparser = subparsers.add_parser('mfcc', | |
| 114 | + help='extract Mel-Frequency Cepstrum Coefficients') | |
| 115 | + subparser.add_input() | |
| 116 | + subparser.add_buf_hop_size() | |
| 117 | + subparser.add_time_format() | |
| 118 | + subparser.add_verbose_help() | |
| 119 | + subparser.set_defaults(process=process_mfcc) | |
| 120 | + | |
| 121 | +def parser_add_subcommand_melbands(subparsers): | |
| 122 | + # melbands subcommand | |
| 123 | + subparser = subparsers.add_parser('melbands', | |
| 124 | + help='extract energies in Mel-frequency bands') | |
| 125 | + subparser.add_input() | |
| 126 | + subparser.add_buf_hop_size() | |
| 127 | + subparser.add_time_format() | |
| 128 | + subparser.add_verbose_help() | |
| 129 | + subparser.set_defaults(process=process_melbands) | |
| 130 | + | |
| 131 | +def parser_add_subcommand_quiet(subparsers): | |
| 132 | + # quiet subcommand | |
| 133 | + subparser = subparsers.add_parser('quiet', | |
| 134 | + help='extract timestamps of quiet and loud regions') | |
| 135 | + subparser.add_input() | |
| 136 | + subparser.add_hop_size() | |
| 137 | + subparser.add_silence() | |
| 138 | + subparser.add_time_format() | |
| 139 | + subparser.add_verbose_help() | |
| 140 | + subparser.set_defaults(process=process_quiet) | |
| 141 | + | |
| 142 | +def parser_add_subcommand_cut(subparsers): | |
| 143 | + # cut subcommand | |
| 144 | + subparser = subparsers.add_parser('cut', | |
| 145 | + help='slice at timestamps') | |
| 146 | + subparser.add_input() | |
| 147 | + helpstr = "onset novelty function" | |
| 148 | + helpstr += " <default|energy|hfc|complex|phase|specdiff|kl|mkl|specflux>" | |
| 149 | + subparser.add_method(helpstr=helpstr) | |
| 150 | + subparser.add_buf_hop_size() | |
| 151 | + subparser.add_silence() | |
| 152 | + subparser.add_threshold(default=0.3) | |
| 153 | + subparser.add_minioi() | |
| 154 | + subparser.add_slicer_options() | |
| 155 | + subparser.add_time_format() | |
| 156 | + subparser.add_verbose_help() | |
| 157 | + subparser.set_defaults(process=process_cut) | |
| 158 | + | |
| 159 | +class AubioArgumentParser(argparse.ArgumentParser): | |
| 160 | + | |
| 161 | + def add_input(self): | |
| 162 | + self.add_argument("source_uri", default=None, nargs='?', | |
| 163 | + help="input sound file to analyse", metavar = "<source_uri>") | |
| 164 | + self.add_argument("-i", "--input", dest = "source_uri2", | |
| 165 | + help="input sound file to analyse", metavar = "<source_uri>") | |
| 166 | + self.add_argument("-r", "--samplerate", | |
| 167 | + metavar = "<freq>", type=int, | |
| 168 | + action="store", dest="samplerate", default=0, | |
| 169 | + help="samplerate at which the file should be represented") | |
| 170 | + | |
| 171 | + def add_verbose_help(self): | |
| 172 | + self.add_argument("-v", "--verbose", | |
| 173 | + action="count", dest="verbose", default=1, | |
| 174 | + help="make lots of noise [default]") | |
| 175 | + self.add_argument("-q", "--quiet", | |
| 176 | + action="store_const", dest="verbose", const=0, | |
| 177 | + help="be quiet") | |
| 178 | + | |
| 179 | + def add_buf_hop_size(self, buf_size=512, hop_size=256): | |
| 180 | + self.add_buf_size(buf_size=buf_size) | |
| 181 | + self.add_hop_size(hop_size=hop_size) | |
| 182 | + | |
| 183 | + def add_buf_size(self, buf_size=512): | |
| 184 | + self.add_argument("-B", "--bufsize", | |
| 185 | + action="store", dest="buf_size", default=buf_size, | |
| 186 | + metavar = "<size>", type=int, | |
| 187 | + help="buffer size [default=%d]" % buf_size) | |
| 188 | + | |
| 189 | + def add_hop_size(self, hop_size=256): | |
| 190 | + self.add_argument("-H", "--hopsize", | |
| 191 | + metavar = "<size>", type=int, | |
| 192 | + action="store", dest="hop_size", default=hop_size, | |
| 193 | + help="overlap size [default=%d]" % hop_size) | |
| 194 | + | |
| 195 | + def add_method(self, method='default', helpstr='method'): | |
| 196 | + self.add_argument("-m", "--method", | |
| 197 | + metavar = "<method>", type=str, | |
| 198 | + action="store", dest="method", default=method, | |
| 199 | + help="%s [default=%s]" % (helpstr, method)) | |
| 200 | + | |
| 201 | + def add_threshold(self, default=None): | |
| 202 | + self.add_argument("-t", "--threshold", | |
| 203 | + metavar = "<threshold>", type=float, | |
| 204 | + action="store", dest="threshold", default=default, | |
| 205 | + help="threshold [default=%s]" % default) | |
| 206 | + | |
| 207 | + def add_silence(self): | |
| 208 | + self.add_argument("-s", "--silence", | |
| 209 | + metavar = "<value>", type=float, | |
| 210 | + action="store", dest="silence", default=-70, | |
| 211 | + help="silence threshold") | |
| 212 | + | |
| 213 | + def add_release_drop(self): | |
| 214 | + self.add_argument("-d", "--release-drop", | |
| 215 | + metavar = "<value>", type=float, | |
| 216 | + action="store", dest="release_drop", default=10, | |
| 217 | + help="release drop threshold") | |
| 218 | + | |
| 219 | + def add_minioi(self, default="12ms"): | |
| 220 | + self.add_argument("-M", "--minioi", | |
| 221 | + metavar = "<value>", type=str, | |
| 222 | + action="store", dest="minioi", default=default, | |
| 223 | + help="minimum Inter-Onset Interval [default=%s]" % default) | |
| 224 | + | |
| 225 | + def add_pitch_unit(self, default="Hz"): | |
| 226 | + help_str = "frequency unit, should be one of Hz, midi, bin, cent" | |
| 227 | + help_str += " [default=%s]" % default | |
| 228 | + self.add_argument("-u", "--pitch-unit", | |
| 229 | + metavar = "<value>", type=str, | |
| 230 | + action="store", dest="pitch_unit", default=default, | |
| 231 | + help=help_str) | |
| 232 | + | |
| 233 | + def add_time_format(self): | |
| 234 | + helpstr = "select time values output format (samples, ms, seconds)" | |
| 235 | + helpstr += " [default=seconds]" | |
| 236 | + self.add_argument("-T", "--time-format", | |
| 237 | + metavar='format', | |
| 238 | + dest="time_format", | |
| 239 | + default=None, | |
| 240 | + help=helpstr) | |
| 241 | + | |
| 242 | + def add_slicer_options(self): | |
| 243 | + self.add_argument("-o", "--output", type = str, | |
| 244 | + metavar = "<outputdir>", | |
| 245 | + action="store", dest="output_directory", default=None, | |
| 246 | + help="specify path where slices of the original file should" | |
| 247 | + " be created") | |
| 248 | + self.add_argument("--cut-until-nsamples", type = int, | |
| 249 | + metavar = "<samples>", | |
| 250 | + action = "store", dest = "cut_until_nsamples", default = None, | |
| 251 | + help="how many extra samples should be added at the end of" | |
| 252 | + " each slice") | |
| 253 | + self.add_argument("--cut-every-nslices", type = int, | |
| 254 | + metavar = "<samples>", | |
| 255 | + action = "store", dest = "cut_every_nslices", default = None, | |
| 256 | + help="how many slices should be groupped together at each cut") | |
| 257 | + self.add_argument("--cut-until-nslices", type = int, | |
| 258 | + metavar = "<slices>", | |
| 259 | + action = "store", dest = "cut_until_nslices", default = None, | |
| 260 | + help="how many extra slices should be added at the end of" | |
| 261 | + " each slice") | |
| 262 | + self.add_argument("--create-first", | |
| 263 | + action = "store_true", dest = "create_first", default = False, | |
| 264 | + help="always include first slice") | |
| 265 | + | |
| 266 | +# some utilities | |
| 267 | + | |
| 268 | +def samples2seconds(n_frames, samplerate): | |
| 269 | + return "%f\t" % (n_frames / float(samplerate)) | |
| 270 | + | |
| 271 | +def samples2milliseconds(n_frames, samplerate): | |
| 272 | + return "%f\t" % (1000. * n_frames / float(samplerate)) | |
| 273 | + | |
| 274 | +def samples2samples(n_frames, _samplerate): | |
| 275 | + return "%d\t" % n_frames | |
| 276 | + | |
| 277 | +def timefunc(mode): | |
| 278 | + if mode is None or mode == 'seconds' or mode == 's': | |
| 279 | + return samples2seconds | |
| 280 | + elif mode == 'ms' or mode == 'milliseconds': | |
| 281 | + return samples2milliseconds | |
| 282 | + elif mode == 'samples': | |
| 283 | + return samples2samples | |
| 284 | + else: | |
| 285 | + raise ValueError("invalid time format '%s'" % mode) | |
| 286 | + | |
| 287 | +# definition of processing classes | |
| 288 | + | |
| 289 | +class default_process(object): | |
| 290 | + def __init__(self, args): | |
| 291 | + if 'time_format' in args: | |
| 292 | + self.time2string = timefunc(args.time_format) | |
| 293 | + if args.verbose > 2 and hasattr(self, 'options'): | |
| 294 | + name = type(self).__name__.split('_')[1] | |
| 295 | + optstr = ' '.join(['running', name, 'with options', | |
| 296 | + repr(self.options), '\n']) | |
| 297 | + sys.stderr.write(optstr) | |
| 298 | + def flush(self, frames_read, samplerate): | |
| 299 | + # optionally called at the end of process | |
| 300 | + pass | |
| 301 | + | |
| 302 | + def parse_options(self, args, valid_opts): | |
| 303 | + # get any valid options found in a dictionnary of arguments | |
| 304 | + options = {k: v for k, v in vars(args).items() if k in valid_opts} | |
| 305 | + self.options = options | |
| 306 | + | |
| 307 | + def remap_pvoc_options(self, options): | |
| 308 | + # FIXME: we need to remap buf_size to win_s, hop_size to hop_s | |
| 309 | + # adjust python/ext/py-phasevoc.c to understand buf_size/hop_size | |
| 310 | + if 'buf_size' in options: | |
| 311 | + options['win_s'] = options['buf_size'] | |
| 312 | + del options['buf_size'] | |
| 313 | + if 'hop_size' in options: | |
| 314 | + options['hop_s'] = options['hop_size'] | |
| 315 | + del options['hop_size'] | |
| 316 | + self.options = options | |
| 317 | + | |
| 318 | +class process_onset(default_process): | |
| 319 | + valid_opts = ['method', 'hop_size', 'buf_size', 'samplerate'] | |
| 320 | + def __init__(self, args): | |
| 321 | + self.parse_options(args, self.valid_opts) | |
| 322 | + self.onset = aubio.onset(**self.options) | |
| 323 | + if args.threshold is not None: | |
| 324 | + self.onset.set_threshold(args.threshold) | |
| 325 | + if args.minioi: | |
| 326 | + if args.minioi.endswith('ms'): | |
| 327 | + self.onset.set_minioi_ms(float(args.minioi[:-2])) | |
| 328 | + elif args.minioi.endswith('s'): | |
| 329 | + self.onset.set_minioi_s(float(args.minioi[:-1])) | |
| 330 | + else: | |
| 331 | + self.onset.set_minioi(int(args.minioi)) | |
| 332 | + if args.silence: | |
| 333 | + self.onset.set_silence(args.silence) | |
| 334 | + super(process_onset, self).__init__(args) | |
| 335 | + def __call__(self, block): | |
| 336 | + return self.onset(block) | |
| 337 | + def repr_res(self, res, _frames_read, samplerate): | |
| 338 | + if res[0] != 0: | |
| 339 | + outstr = self.time2string(self.onset.get_last(), samplerate) | |
| 340 | + sys.stdout.write(outstr + '\n') | |
| 341 | + | |
| 342 | +class process_pitch(default_process): | |
| 343 | + valid_opts = ['method', 'hop_size', 'buf_size', 'samplerate'] | |
| 344 | + def __init__(self, args): | |
| 345 | + self.parse_options(args, self.valid_opts) | |
| 346 | + self.pitch = aubio.pitch(**self.options) | |
| 347 | + if args.pitch_unit is not None: | |
| 348 | + self.pitch.set_unit(args.pitch_unit) | |
| 349 | + if args.threshold is not None: | |
| 350 | + self.pitch.set_tolerance(args.threshold) | |
| 351 | + if args.silence is not None: | |
| 352 | + self.pitch.set_silence(args.silence) | |
| 353 | + super(process_pitch, self).__init__(args) | |
| 354 | + def __call__(self, block): | |
| 355 | + return self.pitch(block) | |
| 356 | + def repr_res(self, res, frames_read, samplerate): | |
| 357 | + fmt_out = self.time2string(frames_read, samplerate) | |
| 358 | + sys.stdout.write(fmt_out + "%.6f\n" % res[0]) | |
| 359 | + | |
| 360 | +class process_beat(default_process): | |
| 361 | + valid_opts = ['method', 'hop_size', 'buf_size', 'samplerate'] | |
| 362 | + def __init__(self, args): | |
| 363 | + self.parse_options(args, self.valid_opts) | |
| 364 | + self.tempo = aubio.tempo(**self.options) | |
| 365 | + super(process_beat, self).__init__(args) | |
| 366 | + def __call__(self, block): | |
| 367 | + return self.tempo(block) | |
| 368 | + def repr_res(self, res, _frames_read, samplerate): | |
| 369 | + if res[0] != 0: | |
| 370 | + outstr = self.time2string(self.tempo.get_last(), samplerate) | |
| 371 | + sys.stdout.write(outstr + '\n') | |
| 372 | + | |
| 373 | +class process_tempo(process_beat): | |
| 374 | + def __init__(self, args): | |
| 375 | + super(process_tempo, self).__init__(args) | |
| 376 | + self.beat_locations = [] | |
| 377 | + def repr_res(self, res, _frames_read, samplerate): | |
| 378 | + if res[0] != 0: | |
| 379 | + self.beat_locations.append(self.tempo.get_last_s()) | |
| 380 | + def flush(self, frames_read, samplerate): | |
| 381 | + import numpy as np | |
| 382 | + if len(self.beat_locations) < 2: | |
| 383 | + outstr = "unknown bpm" | |
| 384 | + else: | |
| 385 | + bpms = 60. / np.diff(self.beat_locations) | |
| 386 | + median_bpm = np.mean(bpms) | |
| 387 | + if len(self.beat_locations) < 10: | |
| 388 | + outstr = "%.2f bpm (uncertain)" % median_bpm | |
| 389 | + else: | |
| 390 | + outstr = "%.2f bpm" % median_bpm | |
| 391 | + sys.stdout.write(outstr + '\n') | |
| 392 | + | |
| 393 | +class process_notes(default_process): | |
| 394 | + valid_opts = ['method', 'hop_size', 'buf_size', 'samplerate'] | |
| 395 | + def __init__(self, args): | |
| 396 | + self.parse_options(args, self.valid_opts) | |
| 397 | + self.notes = aubio.notes(**self.options) | |
| 398 | + if args.silence is not None: | |
| 399 | + self.notes.set_silence(args.silence) | |
| 400 | + if args.release_drop is not None: | |
| 401 | + self.notes.set_release_drop(args.release_drop) | |
| 402 | + super(process_notes, self).__init__(args) | |
| 403 | + def __call__(self, block): | |
| 404 | + return self.notes(block) | |
| 405 | + def repr_res(self, res, frames_read, samplerate): | |
| 406 | + if res[2] != 0: # note off | |
| 407 | + fmt_out = self.time2string(frames_read, samplerate) | |
| 408 | + sys.stdout.write(fmt_out + '\n') | |
| 409 | + if res[0] != 0: # note on | |
| 410 | + lastmidi = res[0] | |
| 411 | + fmt_out = "%f\t" % lastmidi | |
| 412 | + fmt_out += self.time2string(frames_read, samplerate) | |
| 413 | + sys.stdout.write(fmt_out) # + '\t') | |
| 414 | + def flush(self, frames_read, samplerate): | |
| 415 | + eof = self.time2string(frames_read, samplerate) | |
| 416 | + sys.stdout.write(eof + '\n') | |
| 417 | + | |
| 418 | +class process_mfcc(default_process): | |
| 419 | + def __init__(self, args): | |
| 420 | + valid_opts1 = ['hop_size', 'buf_size'] | |
| 421 | + self.parse_options(args, valid_opts1) | |
| 422 | + self.remap_pvoc_options(self.options) | |
| 423 | + self.pv = aubio.pvoc(**self.options) | |
| 424 | + | |
| 425 | + valid_opts2 = ['buf_size', 'n_filters', 'n_coeffs', 'samplerate'] | |
| 426 | + self.parse_options(args, valid_opts2) | |
| 427 | + self.mfcc = aubio.mfcc(**self.options) | |
| 428 | + | |
| 429 | + # remember all options | |
| 430 | + self.parse_options(args, list(set(valid_opts1 + valid_opts2))) | |
| 431 | + | |
| 432 | + super(process_mfcc, self).__init__(args) | |
| 433 | + | |
| 434 | + def __call__(self, block): | |
| 435 | + fftgrain = self.pv(block) | |
| 436 | + return self.mfcc(fftgrain) | |
| 437 | + def repr_res(self, res, frames_read, samplerate): | |
| 438 | + fmt_out = self.time2string(frames_read, samplerate) | |
| 439 | + fmt_out += ' '.join(["% 9.7f" % f for f in res.tolist()]) | |
| 440 | + sys.stdout.write(fmt_out + '\n') | |
| 441 | + | |
| 442 | +class process_melbands(default_process): | |
| 443 | + def __init__(self, args): | |
| 444 | + self.args = args | |
| 445 | + valid_opts = ['hop_size', 'buf_size'] | |
| 446 | + self.parse_options(args, valid_opts) | |
| 447 | + self.remap_pvoc_options(self.options) | |
| 448 | + self.pv = aubio.pvoc(**self.options) | |
| 449 | + | |
| 450 | + valid_opts = ['buf_size', 'n_filters'] | |
| 451 | + self.parse_options(args, valid_opts) | |
| 452 | + self.remap_pvoc_options(self.options) | |
| 453 | + self.filterbank = aubio.filterbank(**self.options) | |
| 454 | + self.filterbank.set_mel_coeffs_slaney(args.samplerate) | |
| 455 | + | |
| 456 | + super(process_melbands, self).__init__(args) | |
| 457 | + def __call__(self, block): | |
| 458 | + fftgrain = self.pv(block) | |
| 459 | + return self.filterbank(fftgrain) | |
| 460 | + def repr_res(self, res, frames_read, samplerate): | |
| 461 | + fmt_out = self.time2string(frames_read, samplerate) | |
| 462 | + fmt_out += ' '.join(["% 9.7f" % f for f in res.tolist()]) | |
| 463 | + sys.stdout.write(fmt_out + '\n') | |
| 464 | + | |
| 465 | +class process_quiet(default_process): | |
| 466 | + def __init__(self, args): | |
| 467 | + self.args = args | |
| 468 | + valid_opts = ['hop_size', 'silence'] | |
| 469 | + self.parse_options(args, valid_opts) | |
| 470 | + self.wassilence = 1 | |
| 471 | + | |
| 472 | + if args.silence is not None: | |
| 473 | + self.silence = args.silence | |
| 474 | + super(process_quiet, self).__init__(args) | |
| 475 | + | |
| 476 | + def __call__(self, block): | |
| 477 | + if aubio.silence_detection(block, self.silence) == 1: | |
| 478 | + if self.wassilence != 1: | |
| 479 | + self.wassilence = 1 | |
| 480 | + return 2 # newly found silence | |
| 481 | + return 1 # silence again | |
| 482 | + else: | |
| 483 | + if self.wassilence != 0: | |
| 484 | + self.wassilence = 0 | |
| 485 | + return -1 # newly found noise | |
| 486 | + return 0 # noise again | |
| 487 | + | |
| 488 | + def repr_res(self, res, frames_read, samplerate): | |
| 489 | + fmt_out = None | |
| 490 | + if res == -1: | |
| 491 | + fmt_out = "NOISY: " | |
| 492 | + if res == 2: | |
| 493 | + fmt_out = "QUIET: " | |
| 494 | + if fmt_out is not None: | |
| 495 | + fmt_out += self.time2string(frames_read, samplerate) | |
| 496 | + sys.stdout.write(fmt_out + '\n') | |
| 497 | + | |
| 498 | +class process_cut(process_onset): | |
| 499 | + def __init__(self, args): | |
| 500 | + super(process_cut, self).__init__(args) | |
| 501 | + self.slices = [] | |
| 502 | + self.options = args | |
| 503 | + | |
| 504 | + def __call__(self, block): | |
| 505 | + ret = super(process_cut, self).__call__(block) | |
| 506 | + if ret: | |
| 507 | + self.slices.append(self.onset.get_last()) | |
| 508 | + return ret | |
| 509 | + | |
| 510 | + def flush(self, frames_read, samplerate): | |
| 511 | + _cut_slice(self.options, self.slices) | |
| 512 | + duration = float(frames_read) / float(samplerate) | |
| 513 | + base_info = '%(source_file)s' % \ | |
| 514 | + {'source_file': self.options.source_uri} | |
| 515 | + base_info += ' (total %(duration).2fs at %(samplerate)dHz)\n' % \ | |
| 516 | + {'duration': duration, 'samplerate': samplerate} | |
| 517 | + info = "created %d slices from " % len(self.slices) | |
| 518 | + info += base_info | |
| 519 | + sys.stderr.write(info) | |
| 520 | + | |
| 521 | +def _cut_slice(options, timestamps): | |
| 522 | + # cutting pass | |
| 523 | + nstamps = len(timestamps) | |
| 524 | + if nstamps > 0: | |
| 525 | + # generate output files | |
| 526 | + timestamps_end = None | |
| 527 | + if options.cut_every_nslices: | |
| 528 | + timestamps = timestamps[::options.cut_every_nslices] | |
| 529 | + nstamps = len(timestamps) | |
| 530 | + if options.cut_until_nslices and options.cut_until_nsamples: | |
| 531 | + msg = "using cut_until_nslices, but cut_until_nsamples is set" | |
| 532 | + warnings.warn(msg) | |
| 533 | + if options.cut_until_nsamples: | |
| 534 | + lag = options.cut_until_nsamples | |
| 535 | + timestamps_end = [t + lag for t in timestamps[1:]] | |
| 536 | + timestamps_end += [1e120] | |
| 537 | + if options.cut_until_nslices: | |
| 538 | + slice_lag = options.cut_until_nslices | |
| 539 | + timestamps_end = [t for t in timestamps[1 + slice_lag:]] | |
| 540 | + timestamps_end += [1e120] * (options.cut_until_nslices + 1) | |
| 541 | + aubio.slice_source_at_stamps(options.source_uri, | |
| 542 | + timestamps, timestamps_end = timestamps_end, | |
| 543 | + output_dir = options.output_directory, | |
| 544 | + samplerate = options.samplerate, | |
| 545 | + create_first = options.create_first) | |
| 546 | + | |
| 547 | +def main(): | |
| 548 | + parser = aubio_parser() | |
| 549 | + if sys.version_info[0] != 3: | |
| 550 | + # on py2, create a dummy ArgumentParser to workaround the | |
| 551 | + # optional subcommand issue. See https://bugs.python.org/issue9253 | |
| 552 | + # This ensures that: | |
| 553 | + # - version string is shown when only '-V' is passed | |
| 554 | + # - help is printed if '-V' is passed with any other argument | |
| 555 | + # - any other argument get forwarded to the real parser | |
| 556 | + parser_root = argparse.ArgumentParser(add_help=False) | |
| 557 | + parser_root.add_argument('-V', '--version', help="show version", | |
| 558 | + action="store_true", dest="show_version") | |
| 559 | + args, extras = parser_root.parse_known_args() | |
| 560 | + if not args.show_version: # no -V, forward to parser | |
| 561 | + args = parser.parse_args(extras, namespace=args) | |
| 562 | + elif len(extras) != 0: # -V with other arguments, print help | |
| 563 | + parser.print_help() | |
| 564 | + sys.exit(1) | |
| 565 | + else: # in py3, we can simply use parser directly | |
| 566 | + args = parser.parse_args() | |
| 567 | + if 'show_version' in args and args.show_version: | |
| 568 | + sys.stdout.write('aubio version ' + aubio.version + '\n') | |
| 569 | + sys.exit(0) | |
| 570 | + elif 'verbose' in args and args.verbose > 3: | |
| 571 | + sys.stderr.write('aubio version ' + aubio.version + '\n') | |
| 572 | + if 'command' not in args or args.command is None \ | |
| 573 | + or args.command in ['help']: | |
| 574 | + # no command given, print help and return 1 | |
| 575 | + parser.print_help() | |
| 576 | + if args.command and args.command in ['help']: | |
| 577 | + sys.exit(0) | |
| 578 | + else: | |
| 579 | + sys.exit(1) | |
| 580 | + elif not args.source_uri and not args.source_uri2: | |
| 581 | + sys.stderr.write("Error: a source is required\n") | |
| 582 | + parser.print_help() | |
| 583 | + sys.exit(1) | |
| 584 | + elif args.source_uri2 is not None: | |
| 585 | + args.source_uri = args.source_uri2 | |
| 586 | + try: | |
| 587 | + # open source_uri | |
| 588 | + with aubio.source(args.source_uri, hop_size=args.hop_size, | |
| 589 | + samplerate=args.samplerate) as a_source: | |
| 590 | + # always update args.samplerate to native samplerate, in case | |
| 591 | + # source was opened with args.samplerate=0 | |
| 592 | + args.samplerate = a_source.samplerate | |
| 593 | + # create the processor for this subcommand | |
| 594 | + processor = args.process(args) | |
| 595 | + frames_read = 0 | |
| 596 | + while True: | |
| 597 | + # read new block from source | |
| 598 | + block, read = a_source() | |
| 599 | + # execute processor on this block | |
| 600 | + res = processor(block) | |
| 601 | + # print results for this block | |
| 602 | + if args.verbose > 0: | |
| 603 | + processor.repr_res(res, frames_read, a_source.samplerate) | |
| 604 | + # increment total number of frames read | |
| 605 | + frames_read += read | |
| 606 | + # exit loop at end of file | |
| 607 | + if read < a_source.hop_size: | |
| 608 | + break | |
| 609 | + # flush the processor if needed | |
| 610 | + processor.flush(frames_read, a_source.samplerate) | |
| 611 | + if args.verbose > 1: | |
| 612 | + fmt_string = "read {:.2f}s" | |
| 613 | + fmt_string += " ({:d} samples in {:d} blocks of {:d})" | |
| 614 | + fmt_string += " from {:s} at {:d}Hz\n" | |
| 615 | + sys.stderr.write(fmt_string.format( | |
| 616 | + frames_read / float(a_source.samplerate), | |
| 617 | + frames_read, | |
| 618 | + frames_read // a_source.hop_size + 1, | |
| 619 | + a_source.hop_size, | |
| 620 | + a_source.uri, | |
| 621 | + a_source.samplerate)) | |
| 622 | + except KeyboardInterrupt: | |
| 623 | + sys.exit(1) | |
| @@ -0,0 +1,163 @@ | ||
| 1 | +#! /usr/bin/env python | |
| 2 | + | |
| 3 | +""" this file was written by Paul Brossier | |
| 4 | + it is released under the GNU/GPL license. | |
| 5 | +""" | |
| 6 | + | |
| 7 | +import sys | |
| 8 | +from aubio.cmd import AubioArgumentParser, _cut_slice | |
| 9 | + | |
| 10 | +def aubio_cut_parser(): | |
| 11 | + parser = AubioArgumentParser() | |
| 12 | + parser.add_input() | |
| 13 | + parser.add_argument("-O", "--onset-method", | |
| 14 | + action="store", dest="onset_method", default='default', | |
| 15 | + metavar = "<onset_method>", | |
| 16 | + help="onset detection method [default=default] \ | |
| 17 | + complexdomain|hfc|phase|specdiff|energy|kl|mkl") | |
| 18 | + # cutting methods | |
| 19 | + parser.add_argument("-b", "--beat", | |
| 20 | + action="store_true", dest="beat", default=False, | |
| 21 | + help="slice at beat locations") | |
| 22 | + """ | |
| 23 | + parser.add_argument("-S", "--silencecut", | |
| 24 | + action="store_true", dest="silencecut", default=False, | |
| 25 | + help="use silence locations") | |
| 26 | + parser.add_argument("-s", "--silence", | |
| 27 | + metavar = "<value>", | |
| 28 | + action="store", dest="silence", default=-70, | |
| 29 | + help="silence threshold [default=-70]") | |
| 30 | + """ | |
| 31 | + # algorithm parameters | |
| 32 | + parser.add_buf_hop_size() | |
| 33 | + parser.add_argument("-t", "--threshold", "--onset-threshold", | |
| 34 | + metavar = "<threshold>", type=float, | |
| 35 | + action="store", dest="threshold", default=0.3, | |
| 36 | + help="onset peak picking threshold [default=0.3]") | |
| 37 | + parser.add_argument("-c", "--cut", | |
| 38 | + action="store_true", dest="cut", default=False, | |
| 39 | + help="cut input sound file at detected labels") | |
| 40 | + parser.add_minioi() | |
| 41 | + | |
| 42 | + """ | |
| 43 | + parser.add_argument("-D", "--delay", | |
| 44 | + action = "store", dest = "delay", type = float, | |
| 45 | + metavar = "<seconds>", default=0, | |
| 46 | + help="number of seconds to take back [default=system]\ | |
| 47 | + default system delay is 3*hopsize/samplerate") | |
| 48 | + parser.add_argument("-C", "--dcthreshold", | |
| 49 | + metavar = "<value>", | |
| 50 | + action="store", dest="dcthreshold", default=1., | |
| 51 | + help="onset peak picking DC component [default=1.]") | |
| 52 | + parser.add_argument("-L", "--localmin", | |
| 53 | + action="store_true", dest="localmin", default=False, | |
| 54 | + help="use local minima after peak detection") | |
| 55 | + parser.add_argument("-d", "--derivate", | |
| 56 | + action="store_true", dest="derivate", default=False, | |
| 57 | + help="derivate onset detection function") | |
| 58 | + parser.add_argument("-z", "--zerocross", | |
| 59 | + metavar = "<value>", | |
| 60 | + action="store", dest="zerothres", default=0.008, | |
| 61 | + help="zero-crossing threshold for slicing [default=0.00008]") | |
| 62 | + # plotting functions | |
| 63 | + parser.add_argument("-p", "--plot", | |
| 64 | + action="store_true", dest="plot", default=False, | |
| 65 | + help="draw plot") | |
| 66 | + parser.add_argument("-x", "--xsize", | |
| 67 | + metavar = "<size>", | |
| 68 | + action="store", dest="xsize", default=1., | |
| 69 | + type=float, help="define xsize for plot") | |
| 70 | + parser.add_argument("-y", "--ysize", | |
| 71 | + metavar = "<size>", | |
| 72 | + action="store", dest="ysize", default=1., | |
| 73 | + type=float, help="define ysize for plot") | |
| 74 | + parser.add_argument("-f", "--function", | |
| 75 | + action="store_true", dest="func", default=False, | |
| 76 | + help="print detection function") | |
| 77 | + parser.add_argument("-n", "--no-onsets", | |
| 78 | + action="store_true", dest="nplot", default=False, | |
| 79 | + help="do not plot detected onsets") | |
| 80 | + parser.add_argument("-O", "--outplot", | |
| 81 | + metavar = "<output_image>", | |
| 82 | + action="store", dest="outplot", default=None, | |
| 83 | + help="save plot to output.{ps,png}") | |
| 84 | + parser.add_argument("-F", "--spectrogram", | |
| 85 | + action="store_true", dest="spectro", default=False, | |
| 86 | + help="add spectrogram to the plot") | |
| 87 | + """ | |
| 88 | + parser.add_slicer_options() | |
| 89 | + parser.add_verbose_help() | |
| 90 | + return parser | |
| 91 | + | |
| 92 | + | |
| 93 | +def _cut_analyze(options): | |
| 94 | + hopsize = options.hop_size | |
| 95 | + bufsize = options.buf_size | |
| 96 | + samplerate = options.samplerate | |
| 97 | + source_uri = options.source_uri | |
| 98 | + | |
| 99 | + # analyze pass | |
| 100 | + from aubio import onset, tempo, source | |
| 101 | + | |
| 102 | + s = source(source_uri, samplerate, hopsize) | |
| 103 | + if samplerate == 0: | |
| 104 | + samplerate = s.samplerate | |
| 105 | + options.samplerate = samplerate | |
| 106 | + | |
| 107 | + if options.beat: | |
| 108 | + o = tempo(options.onset_method, bufsize, hopsize, | |
| 109 | + samplerate=samplerate) | |
| 110 | + else: | |
| 111 | + o = onset(options.onset_method, bufsize, hopsize, | |
| 112 | + samplerate=samplerate) | |
| 113 | + if options.minioi: | |
| 114 | + if options.minioi.endswith('ms'): | |
| 115 | + o.set_minioi_ms(int(options.minioi[:-2])) | |
| 116 | + elif options.minioi.endswith('s'): | |
| 117 | + o.set_minioi_s(int(options.minioi[:-1])) | |
| 118 | + else: | |
| 119 | + o.set_minioi(int(options.minioi)) | |
| 120 | + o.set_threshold(options.threshold) | |
| 121 | + | |
| 122 | + timestamps = [] | |
| 123 | + total_frames = 0 | |
| 124 | + while True: | |
| 125 | + samples, read = s() | |
| 126 | + if o(samples): | |
| 127 | + timestamps.append(o.get_last()) | |
| 128 | + if options.verbose: | |
| 129 | + print("%.4f" % o.get_last_s()) | |
| 130 | + total_frames += read | |
| 131 | + if read < hopsize: | |
| 132 | + break | |
| 133 | + del s | |
| 134 | + return timestamps, total_frames | |
| 135 | + | |
| 136 | +def main(): | |
| 137 | + parser = aubio_cut_parser() | |
| 138 | + options = parser.parse_args() | |
| 139 | + if not options.source_uri and not options.source_uri2: | |
| 140 | + sys.stderr.write("Error: no file name given\n") | |
| 141 | + parser.print_help() | |
| 142 | + sys.exit(1) | |
| 143 | + elif options.source_uri2 is not None: | |
| 144 | + options.source_uri = options.source_uri2 | |
| 145 | + | |
| 146 | + # analysis | |
| 147 | + timestamps, total_frames = _cut_analyze(options) | |
| 148 | + | |
| 149 | + # print some info | |
| 150 | + duration = float(total_frames) / float(options.samplerate) | |
| 151 | + base_info = '%(source_uri)s' % {'source_uri': options.source_uri} | |
| 152 | + base_info += ' (total %(duration).2fs at %(samplerate)dHz)\n' % \ | |
| 153 | + {'duration': duration, 'samplerate': options.samplerate} | |
| 154 | + | |
| 155 | + info = "found %d timestamps in " % len(timestamps) | |
| 156 | + info += base_info | |
| 157 | + sys.stderr.write(info) | |
| 158 | + | |
| 159 | + if options.cut: | |
| 160 | + _cut_slice(options, timestamps) | |
| 161 | + info = "created %d slices from " % len(timestamps) | |
| 162 | + info += base_info | |
| 163 | + sys.stderr.write(info) | |
| @@ -1,9 +1,11 @@ | ||
| 1 | 1 | # -*- coding: utf-8 -*- |
| 2 | 2 | """ utilities to convert midi note number to and from note names """ |
| 3 | 3 | |
| 4 | −__all__ = ['note2midi', 'midi2note', 'freq2note'] | |
| 5 | − | |
| 6 | 4 | import sys |
| 5 | +from ._aubio import freqtomidi, miditofreq | |
| 6 | + | |
| 7 | +__all__ = ['note2midi', 'midi2note', 'freq2note', 'note2freq'] | |
| 8 | + | |
| 7 | 9 | py3 = sys.version_info[0] == 3 |
| 8 | 10 | if py3: |
| 9 | 11 | str_instances = str |
| @@ -12,18 +14,65 @@ else: · 2 unmodified lines | ||
| 12 | 14 | str_instances = (str, unicode) |
| 13 | 15 | int_instances = (int, long) |
| 14 | 16 | |
| 17 | + | |
| 15 | 18 | def 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 | + } | |
| 20 | 68 | _valid_octaves = range(-1, 10) |
| 21 | 69 | 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))) | |
| 23 | 72 | 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 | |
| 27 | 76 | |
| 28 | 77 | if len(note) == 4: |
| 29 | 78 | notename, modifier, octave_sign, octave = note |
| @@ -46,21 +95,97 @@ def note2midi(note): · 16 unmodified lines | ||
| 46 | 95 | if octave not in _valid_octaves: |
| 47 | 96 | raise ValueError("%s is not a valid octave" % octave) |
| 48 | 97 | |
| 49 | − midi = 12 + octave * 12 + _valid_notenames[notename] + _valid_modifiers[modifier] | |
| 98 | + midi = (octave + 1) * 12 + _valid_notenames[notename] \ | |
| 99 | + + _valid_modifiers[modifier] | |
| 50 | 100 | if midi > 127: |
| 51 | 101 | raise ValueError("%s is outside of the range C-2 to G8" % note) |
| 52 | 102 | return midi |
| 53 | 103 | |
| 104 | + | |
| 54 | 105 | def 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 | + """ | |
| 56 | 136 | if not isinstance(midi, int_instances): |
| 57 | 137 | raise TypeError("an integer is required, got %s" % midi) |
| 58 | 138 | 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'] | |
| 61 | 143 | return _valid_notenames[midi % 12] + str(int(midi / 12) - 1) |
| 62 | 144 | |
| 145 | + | |
| 63 | 146 | def 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 | + | |
| 170 | +def 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) | |
| @@ -5,26 +5,91 @@ from aubio import source, sink · 4 unmodified lines | ||
| 5 | 5 | |
| 6 | 6 | _max_timestamp = 1e120 |
| 7 | 7 | |
| 8 | + | |
| 8 | 9 | def 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. | |
| 13 | + | |
| 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 | + """ | |
| 11 | 75 | |
| 12 | − if timestamps is None or len(timestamps) == 0: | |
| 76 | + if not timestamps: | |
| 13 | 77 | raise ValueError("no timestamps given") |
| 14 | 78 | |
| 15 | − if timestamps[0] != 0: | |
| 79 | + if timestamps[0] != 0 and create_first: | |
| 16 | 80 | timestamps = [0] + timestamps |
| 17 | 81 | if timestamps_end is not None: |
| 18 | 82 | timestamps_end = [timestamps[1] - 1] + timestamps_end |
| 19 | 83 | |
| 20 | 84 | 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): | |
| 22 | 88 | raise ValueError("len(timestamps_end) != len(timestamps)") |
| 23 | 89 | else: |
| 24 | 90 | timestamps_end = [t - 1 for t in timestamps[1:]] + [_max_timestamp] |
| 25 | 91 | |
| 26 | 92 | regions = list(zip(timestamps, timestamps_end)) |
| 27 | − #print regions | |
| 28 | 93 | |
| 29 | 94 | source_base_name, _ = os.path.splitext(os.path.basename(source_file)) |
| 30 | 95 | if output_dir is not None: |
| @@ -32,8 +97,8 @@ def slice_source_at_stamps(source_file, timestamps, timestamps_end=None, · 1 unmodified line | ||
| 32 | 97 | os.makedirs(output_dir) |
| 33 | 98 | source_base_name = os.path.join(output_dir, source_base_name) |
| 34 | 99 | |
| 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 | |
| 37 | 102 | timestamp_seconds = timestamp / float(samplerate) |
| 38 | 103 | return source_base_name + "_%011.6f" % timestamp_seconds + '.wav' |
| 39 | 104 | |
| @@ -48,16 +113,17 @@ def slice_source_at_stamps(source_file, timestamps, timestamps_end=None, · 8 unmodified lines | ||
| 48 | 113 | # get hopsize new samples from source |
| 49 | 114 | vec, read = _source.do_multi() |
| 50 | 115 | # 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]: | |
| 53 | 117 | # get next region |
| 54 | 118 | start_stamp, end_stamp = regions.pop(0) |
| 55 | 119 | # 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) | |
| 57 | 122 | # create its sink |
| 58 | 123 | _sink = sink(new_sink_path, samplerate, _source.channels) |
| 59 | 124 | # 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} | |
| 61 | 127 | # append the dictionary to the current list of slices |
| 62 | 128 | slices.append(new_slice) |
| 63 | 129 | |
| @@ -69,18 +135,19 @@ def slice_source_at_stamps(source_file, timestamps, timestamps_end=None, · 5 unmodified lines | ||
| 69 | 135 | start = max(start_stamp - total_frames, 0) |
| 70 | 136 | # number of samples yet to written be until end of region |
| 71 | 137 | remaining = end_stamp - total_frames + 1 |
| 72 | − #print current_slice, remaining, start | |
| 73 | 138 | # not enough frames remaining, time to split |
| 74 | 139 | if remaining < read: |
| 75 | 140 | if remaining > start: |
| 76 | 141 | # write remaining samples from current region |
| 77 | 142 | _sink.do_multi(vec[:, start:remaining], remaining - start) |
| 78 | − #print "closing region", "remaining", remaining | |
| 79 | 143 | # close this file |
| 80 | 144 | _sink.close() |
| 81 | 145 | elif read > start: |
| 82 | 146 | # write all the samples |
| 83 | 147 | _sink.do_multi(vec[:, start:read], read - start) |
| 84 | 148 | total_frames += read |
| 149 | + # remove old slices | |
| 150 | + slices = list(filter(lambda s: s['end_stamp'] > total_frames, | |
| 151 | + slices)) | |
| 85 | 152 | if read < hopsize: |
| 86 | 153 | break |
| @@ -2,6 +2,7 @@ aubiodefvalue = { · 1 unmodified line | ||
| 2 | 2 | # we have some clean up to do |
| 3 | 3 | 'buf_size': 'Py_default_vector_length', |
| 4 | 4 | 'win_s': 'Py_default_vector_length', |
| 5 | + 'size': 'Py_default_vector_length', | |
| 5 | 6 | # and here too |
| 6 | 7 | 'hop_size': 'Py_default_vector_length / 2', |
| 7 | 8 | 'hop_s': 'Py_default_vector_length / 2', |
| @@ -84,6 +85,7 @@ objoutsize = { · 76 unmodified lines | ||
| 84 | 85 | 'filterbank': 'self->n_filters', |
| 85 | 86 | 'tss': 'self->buf_size', |
| 86 | 87 | 'pitchshift': 'self->hop_size', |
| 88 | + 'dct': 'self->size', | |
| 87 | 89 | } |
| 88 | 90 | |
| 89 | 91 | objinputsize = { |
| @@ -179,6 +181,10 @@ class MappedObject(object): · 89 unmodified lines | ||
| 179 | 181 | self.do_inputs = [get_params_types_names(self.do_proto)[1]] |
| 180 | 182 | self.do_outputs = get_params_types_names(self.do_proto)[2:] |
| 181 | 183 | 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 | |
| 182 | 188 | self.struct_outputs = ";\n ".join(struct_output_str) |
| 183 | 189 | |
| 184 | 190 | #print ("input_params: ", map(split_type, get_input_params(self.do_proto))) |
| @@ -186,17 +192,26 @@ class MappedObject(object): · 1 unmodified line | ||
| 186 | 192 | |
| 187 | 193 | def gen_code(self): |
| 188 | 194 | 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 | |
| 200 | 215 | return out |
| 201 | 216 | |
| 202 | 217 | def gen_struct(self): |
| @@ -380,12 +395,12 @@ Py_{shortname}_del (Py_{shortname} * self, PyObject * unused) · 177 unmodified lines | ||
| 380 | 395 | """.format(del_fn = del_fn) |
| 381 | 396 | return out |
| 382 | 397 | |
| 383 | − def gen_do(self): | |
| 398 | + def gen_do(self, method = 'do'): | |
| 384 | 399 | out = """ |
| 385 | 400 | // do {shortname} |
| 386 | 401 | static PyObject* |
| 387 | −Py_{shortname}_do (Py_{shortname} * self, PyObject * args) | |
| 388 | −{{""".format(**self.__dict__) | |
| 402 | +Pyaubio_{shortname}_{method} (Py_{shortname} * self, PyObject * args) | |
| 403 | +{{""".format(method = method, **self.__dict__) | |
| 389 | 404 | input_params = self.do_inputs |
| 390 | 405 | output_params = self.do_outputs |
| 391 | 406 | #print input_params |
| @@ -461,30 +476,51 @@ Py_{shortname}_do (Py_{shortname} * self, PyObject * args) · 69 unmodified lines | ||
| 461 | 476 | // {shortname} setters |
| 462 | 477 | """.format(**self.__dict__) |
| 463 | 478 | 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]) | |
| 466 | 483 | method_name = get_name(set_param) |
| 467 | 484 | 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]) | |
| 469 | 490 | out += """ |
| 470 | 491 | static PyObject * |
| 471 | 492 | Pyaubio_{shortname}_set_{param} (Py_{shortname} *self, PyObject *args) |
| 472 | 493 | {{ |
| 473 | 494 | uint_t err = 0; |
| 474 | − {paramtype} {param}; | |
| 495 | + {paramdecls} | |
| 496 | +""".format(param = param, paramdecls = paramdecls, **self.__dict__) | |
| 497 | + | |
| 498 | + if len(refs) and len(pyparamtypes): | |
| 499 | + out += """ | |
| 475 | 500 | |
| 476 | − if (!PyArg_ParseTuple (args, "{pyparamtype}", &{param})) {{ | |
| 501 | + if (!PyArg_ParseTuple (args, "{pyparamtypes}", {refs})) {{ | |
| 477 | 502 | return NULL; |
| 478 | 503 | }} |
| 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}); | |
| 480 | 508 | |
| 481 | 509 | 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 | + }} | |
| 483 | 518 | return NULL; |
| 484 | 519 | }} |
| 485 | 520 | Py_RETURN_NONE; |
| 486 | 521 | }} |
| 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__) | |
| 488 | 524 | return out |
| 489 | 525 | |
| 490 | 526 | def gen_get(self): |
| @@ -525,6 +561,12 @@ static PyMethodDef Py_{shortname}_methods[] = {{""".format(**self.__dict__) · 34 unmodified lines | ||
| 525 | 561 | out += """ |
| 526 | 562 | {{"{shortname}", (PyCFunction) Py{name}, |
| 527 | 563 | 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) | |
| 528 | 570 | out += """ |
| 529 | 571 | {NULL} /* sentinel */ |
| 530 | 572 | }; |
| @@ -550,7 +592,7 @@ PyTypeObject Py_{shortname}Type = {{ · 19 unmodified lines | ||
| 550 | 592 | 0, |
| 551 | 593 | 0, |
| 552 | 594 | 0, |
| 553 | − (ternaryfunc)Py_{shortname}_do, | |
| 595 | + (ternaryfunc)Pyaubio_{shortname}_do, | |
| 554 | 596 | 0, |
| 555 | 597 | 0, |
| 556 | 598 | 0, |
| @@ -1,5 +1,10 @@ | ||
| 1 | 1 | import distutils.ccompiler |
| 2 | −import sys, os, subprocess, glob | |
| 2 | +import sys | |
| 3 | +import os | |
| 4 | +import subprocess | |
| 5 | +import glob | |
| 6 | +from distutils.sysconfig import customize_compiler | |
| 7 | +from gen_code import MappedObject | |
| 3 | 8 | |
| 4 | 9 | header = os.path.join('src', 'aubio.h') |
| 5 | 10 | output_path = os.path.join('python', 'gen') |
| @@ -8,43 +13,44 @@ source_header = """// this file is generated! do not modify · 2 unmodified lines | ||
| 8 | 13 | #include "aubio-types.h" |
| 9 | 14 | """ |
| 10 | 15 | |
| 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 | − ] | |
| 16 | +default_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 | + | |
| 44 | 51 | |
| 45 | 52 | def get_preprocessor(): |
| 46 | 53 | # findout which compiler to use |
| 47 | − from distutils.sysconfig import customize_compiler | |
| 48 | 54 | compiler_name = distutils.ccompiler.get_default_compiler() |
| 49 | 55 | compiler = distutils.ccompiler.new_compiler(compiler=compiler_name) |
| 50 | 56 | try: |
| @@ -59,26 +65,50 @@ def get_preprocessor(): · 8 unmodified lines | ||
| 59 | 65 | print("Warning: failed initializing compiler ({:s})".format(repr(e))) |
| 60 | 66 | |
| 61 | 67 | cpp_cmd = None |
| 62 | − if hasattr(compiler, 'preprocessor'): # for unixccompiler | |
| 68 | + if hasattr(compiler, 'preprocessor'): # for unixccompiler | |
| 63 | 69 | cpp_cmd = compiler.preprocessor |
| 64 | − elif hasattr(compiler, 'compiler'): # for ccompiler | |
| 70 | + elif hasattr(compiler, 'compiler'): # for ccompiler | |
| 65 | 71 | cpp_cmd = compiler.compiler.split() |
| 66 | 72 | cpp_cmd += ['-E'] |
| 67 | − elif hasattr(compiler, 'cc'): # for msvccompiler | |
| 73 | + elif hasattr(compiler, 'cc'): # for msvccompiler | |
| 68 | 74 | cpp_cmd = compiler.cc.split() |
| 69 | 75 | cpp_cmd += ['-E'] |
| 70 | 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) | |
| 88 | + | |
| 71 | 89 | if not cpp_cmd: |
| 72 | 90 | print("Warning: could not guess preprocessor, using env's CC") |
| 73 | 91 | cpp_cmd = os.environ.get('CC', 'cc').split() |
| 74 | 92 | cpp_cmd += ['-E'] |
| 75 | − | |
| 93 | + if 'emcc' in cpp_cmd: | |
| 94 | + cpp_cmd += ['-x', 'c'] # emcc defaults to c++, force C language | |
| 76 | 95 | return cpp_cmd |
| 77 | 96 | |
| 78 | −def get_cpp_objects(header=header): | |
| 97 | + | |
| 98 | +def 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 | + | |
| 105 | +def get_cpp_output(header=header, usedouble=False): | |
| 106 | + ''' find and run a C pre-processor on aubio.h ''' | |
| 79 | 107 | cpp_cmd = get_preprocessor() |
| 80 | 108 | |
| 81 | 109 | macros = [('AUBIO_UNSTABLE', 1)] |
| 110 | + if usedouble: | |
| 111 | + macros += [('HAVE_AUBIO_DOUBLE', 1)] | |
| 82 | 112 | |
| 83 | 113 | if not os.path.isfile(header): |
| 84 | 114 | raise Exception("could not find include file " + header) |
| @@ -89,56 +119,115 @@ def get_cpp_objects(header=header): · 4 unmodified lines | ||
| 89 | 119 | |
| 90 | 120 | print("Running command: {:s}".format(" ".join(cpp_cmd))) |
| 91 | 121 | proc = subprocess.Popen(cpp_cmd, |
| 92 | − stderr=subprocess.PIPE, | |
| 93 | − stdout=subprocess.PIPE) | |
| 122 | + stderr=subprocess.PIPE, | |
| 123 | + stdout=subprocess.PIPE) | |
| 94 | 124 | assert proc, 'Proc was none' |
| 95 | 125 | cpp_output = proc.stdout.read() |
| 96 | 126 | err_output = proc.stderr.read() |
| 127 | + if err_output: | |
| 128 | + print("Warning: preprocessor produced errors or warnings:\n%s" \ | |
| 129 | + % err_output.decode('utf8')) | |
| 97 | 130 | 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) | |
| 101 | 138 | if not isinstance(cpp_output, list): |
| 102 | 139 | cpp_output = [l.strip() for l in cpp_output.decode('utf8').split('\n')] |
| 103 | 140 | |
| 104 | − cpp_output = filter(lambda y: len(y) > 1, cpp_output) | |
| 141 | + return cpp_output | |
| 142 | + | |
| 143 | +def filter_cpp_output(cpp_raw_output): | |
| 144 | + ''' prepare cpp-output for parsing ''' | |
| 145 | + cpp_output = filter(lambda y: len(y) > 1, cpp_raw_output) | |
| 105 | 146 | cpp_output = list(filter(lambda y: not y.startswith('#'), cpp_output)) |
| 106 | 147 | |
| 107 | 148 | i = 1 |
| 108 | 149 | 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) | |
| 113 | 158 | else: |
| 114 | 159 | i += 1 |
| 115 | 160 | |
| 116 | − typedefs = filter(lambda y: y.startswith ('typedef struct _aubio'), cpp_output) | |
| 161 | + # clean pointer notations | |
| 162 | + tmp = [] | |
| 163 | + for l in cpp_output: | |
| 164 | + tmp += [l.replace(' *', ' * ')] | |
| 165 | + cpp_output = tmp | |
| 117 | 166 | |
| 167 | + return cpp_output | |
| 168 | + | |
| 169 | + | |
| 170 | +def 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) | |
| 118 | 174 | cpp_objects = [a.split()[3][:-1] for a in typedefs] |
| 175 | + cpp_objects_filtered = filter(lambda y: not y[6:-2] in skip_objects, cpp_objects) | |
| 176 | + return cpp_objects_filtered | |
| 177 | + | |
| 178 | + | |
| 179 | +def 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)) | |
| 119 | 197 | |
| 120 | − return cpp_output, cpp_objects | |
| 198 | + return res | |
| 121 | 199 | |
| 122 | 200 | |
| 123 | −def analyze_cpp_output(cpp_objects, cpp_output): | |
| 201 | +def 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 | + ''' | |
| 124 | 208 | lib = {} |
| 125 | 209 | |
| 126 | 210 | 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': []} | |
| 133 | 216 | lib[shortname]['longname'] = o |
| 134 | 217 | 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 | |
| 138 | 225 | if 'typedef struct ' in fn: |
| 139 | 226 | lib[shortname]['struct'].append(fn) |
| 140 | 227 | elif '_do' in fn: |
| 141 | 228 | lib[shortname]['do'].append(fn) |
| 229 | + elif '_rdo' in fn: | |
| 230 | + lib[shortname]['rdo'].append(fn) | |
| 142 | 231 | elif 'new_' in fn: |
| 143 | 232 | lib[shortname]['new'].append(fn) |
| 144 | 233 | elif 'del_' in fn: |
| @@ -148,52 +237,52 @@ def analyze_cpp_output(cpp_objects, cpp_output): · 3 unmodified lines | ||
| 148 | 237 | elif '_set_' in fn: |
| 149 | 238 | lib[shortname]['set'].append(fn) |
| 150 | 239 | else: |
| 151 | − #print "no idea what to do about", fn | |
| 240 | + # print "no idea what to do about", fn | |
| 152 | 241 | lib[shortname]['other'].append(fn) |
| 153 | 242 | return lib |
| 154 | 243 | |
| 155 | −def print_cpp_output_results(lib, cpp_output): | |
| 156 | − for fn in cpp_output: | |
| 244 | + | |
| 245 | +def print_c_declarations_results(lib, c_declarations): | |
| 246 | + for fn in c_declarations: | |
| 157 | 247 | found = 0 |
| 158 | 248 | for o in lib: |
| 159 | 249 | for family in lib[o]: |
| 160 | 250 | if fn in lib[o][family]: |
| 161 | 251 | found = 1 |
| 162 | 252 | if found == 0: |
| 163 | − print ("missing", fn) | |
| 253 | + print("missing", fn) | |
| 164 | 254 | |
| 165 | 255 | for o in lib: |
| 166 | 256 | for family in lib[o]: |
| 167 | 257 | 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])) | |
| 169 | 259 | 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])) | |
| 171 | 261 | else: |
| 172 | − print ( "{:15s} {:10s} {:s}".format(o, family, lib[o][family] ) ) | |
| 262 | + print("{:15s} {:10s} {:s}".format(o, family, lib[o][family])) | |
| 173 | 263 | |
| 174 | 264 | |
| 175 | 265 | def 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')) | |
| 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'))) | |
| 178 | 270 | |
| 179 | − cpp_output, cpp_objects = get_cpp_objects(header) | |
| 271 | + c_declarations = get_c_declarations(header, usedouble=usedouble) | |
| 272 | + cpp_objects = get_cpp_objects_from_c_declarations(c_declarations) | |
| 180 | 273 | |
| 181 | − lib = analyze_cpp_output(cpp_objects, cpp_output) | |
| 182 | − # print_cpp_output_results(lib, cpp_output) | |
| 274 | + lib = generate_lib_from_c_declarations(cpp_objects, c_declarations) | |
| 275 | + # print_c_declarations_results(lib, c_declarations) | |
| 183 | 276 | |
| 184 | 277 | sources_list = [] |
| 185 | − try: | |
| 186 | − from .gen_code import MappedObject | |
| 187 | − except (SystemError, ValueError): | |
| 188 | − from gen_code import MappedObject | |
| 189 | 278 | for o in lib: |
| 190 | 279 | out = source_header |
| 191 | − mapped = MappedObject(lib[o], usedouble = usedouble) | |
| 280 | + mapped = MappedObject(lib[o], usedouble=usedouble) | |
| 192 | 281 | out += mapped.gen_code() |
| 193 | 282 | output_file = os.path.join(output_path, 'gen-%s.c' % o) |
| 194 | 283 | with open(output_file, 'w') as f: |
| 195 | 284 | f.write(out) |
| 196 | − print ("wrote %s" % output_file ) | |
| 285 | + print("wrote %s" % output_file) | |
| 197 | 286 | sources_list.append(output_file) |
| 198 | 287 | |
| 199 | 288 | out = source_header |
| @@ -205,23 +294,23 @@ int generated_types_ready (void) · 5 unmodified lines | ||
| 205 | 294 | {{ |
| 206 | 295 | return ({pycheck_types}); |
| 207 | 296 | }} |
| 208 | −""".format(pycheck_types = check_types) | |
| 297 | +""".format(pycheck_types=check_types) | |
| 209 | 298 | |
| 210 | 299 | add_types = "".join([""" |
| 211 | 300 | 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]) | |
| 213 | 302 | out += """ |
| 214 | 303 | |
| 215 | 304 | void add_generated_objects ( PyObject *m ) |
| 216 | 305 | {{ |
| 217 | 306 | {add_types} |
| 218 | 307 | }} |
| 219 | −""".format(add_types = add_types) | |
| 308 | +""".format(add_types=add_types) | |
| 220 | 309 | |
| 221 | 310 | output_file = os.path.join(output_path, 'aubio-generated.c') |
| 222 | 311 | with open(output_file, 'w') as f: |
| 223 | 312 | f.write(out) |
| 224 | − print ("wrote %s" % output_file ) | |
| 313 | + print("wrote %s" % output_file) | |
| 225 | 314 | sources_list.append(output_file) |
| 226 | 315 | |
| 227 | 316 | objlist = "".join(["extern PyTypeObject Py_%sType;\n" % p for p in lib]) |
| @@ -239,17 +328,19 @@ void add_generated_objects ( PyObject *m ) · 11 unmodified lines | ||
| 239 | 328 | {objlist} |
| 240 | 329 | int generated_objects ( void ); |
| 241 | 330 | void add_generated_objects( PyObject *m ); |
| 242 | −""".format(objlist = objlist) | |
| 331 | +""".format(objlist=objlist) | |
| 243 | 332 | |
| 244 | 333 | output_file = os.path.join(output_path, 'aubio-generated.h') |
| 245 | 334 | with open(output_file, 'w') as f: |
| 246 | 335 | f.write(out) |
| 247 | − print ("wrote %s" % output_file ) | |
| 336 | + print("wrote %s" % output_file) | |
| 248 | 337 | # no need to add header to list of sources |
| 249 | 338 | |
| 250 | − return sources_list | |
| 339 | + return sorted(sources_list) | |
| 251 | 340 | |
| 252 | 341 | if __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] | |
| 255 | 346 | generate_external(header, output_path) |
| @@ -2,7 +2,9 @@ · 1 unmodified line | ||
| 2 | 2 | # |
| 3 | 3 | import sys, os, glob, subprocess |
| 4 | 4 | import distutils, distutils.command.clean, distutils.dir_util |
| 5 | −from .gen_external import generate_external, header, output_path | |
| 5 | +from gen_external import generate_external, header, output_path | |
| 6 | + | |
| 7 | +from this_version import get_aubio_version | |
| 6 | 8 | |
| 7 | 9 | # inspired from https://gist.github.com/abergmeier/9488990 |
| 8 | 10 | def add_packages(packages, ext=None, **kw): |
| @@ -21,6 +23,7 @@ def add_packages(packages, ext=None, **kw): · 12 unmodified lines | ||
| 21 | 23 | } |
| 22 | 24 | |
| 23 | 25 | for package in packages: |
| 26 | + print("checking for {:s}".format(package)) | |
| 24 | 27 | cmd = ['pkg-config', '--libs', '--cflags', package] |
| 25 | 28 | try: |
| 26 | 29 | tokens = subprocess.check_output(cmd) |
| @@ -54,13 +57,14 @@ def add_local_aubio_lib(ext): · 27 unmodified lines | ||
| 54 | 57 | |
| 55 | 58 | def add_local_aubio_sources(ext): |
| 56 | 59 | """ 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'))) | |
| 63 | 63 | ext.sources += aubio_sources |
| 64 | + | |
| 65 | +def add_local_macros(ext, usedouble = False): | |
| 66 | + if usedouble: | |
| 67 | + ext.define_macros += [('HAVE_AUBIO_DOUBLE', 1)] | |
| 64 | 68 | # define macros (waf puts them in build/src/config.h) |
| 65 | 69 | for define_macro in ['HAVE_STDLIB_H', 'HAVE_STDIO_H', |
| 66 | 70 | 'HAVE_MATH_H', 'HAVE_STRING_H', |
| @@ -69,22 +73,31 @@ def add_local_aubio_sources(ext): · 2 unmodified lines | ||
| 69 | 73 | 'HAVE_MEMCPY_HACKS']: |
| 70 | 74 | ext.define_macros += [(define_macro, 1)] |
| 71 | 75 | |
| 76 | +def add_external_deps(ext, usedouble = False): | |
| 72 | 77 | # loof for additional packages |
| 73 | 78 | print("Info: looking for *optional* additional packages") |
| 74 | − packages = ['libavcodec', 'libavformat', 'libavutil', 'libavresample', | |
| 79 | + packages = ['libavcodec', 'libavformat', 'libavutil', | |
| 80 | + 'libswresample', 'libavresample', | |
| 75 | 81 | 'jack', |
| 76 | − 'sndfile', 'samplerate', | |
| 82 | + 'sndfile', | |
| 77 | 83 | 'rubberband', |
| 78 | 84 | #'fftw3f', |
| 79 | 85 | ] |
| 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") | |
| 80 | 91 | add_packages(packages, ext=ext) |
| 81 | 92 | if 'avcodec' in ext.libraries \ |
| 82 | 93 | 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)] | |
| 88 | 101 | if 'sndfile' in ext.libraries: |
| 89 | 102 | ext.define_macros += [('HAVE_SNDFILE', 1)] |
| 90 | 103 | if 'samplerate' in ext.libraries: |
| @@ -107,39 +120,78 @@ def add_local_aubio_sources(ext): · 16 unmodified lines | ||
| 107 | 120 | |
| 108 | 121 | ext.define_macros += [('HAVE_WAVWRITE', 1)] |
| 109 | 122 | ext.define_macros += [('HAVE_WAVREAD', 1)] |
| 110 | − # TODO: | |
| 111 | − # add cblas | |
| 123 | + | |
| 124 | + # TODO: add cblas | |
| 112 | 125 | if 0: |
| 113 | 126 | ext.libraries += ['cblas'] |
| 114 | 127 | ext.define_macros += [('HAVE_ATLAS_CBLAS_H', 1)] |
| 115 | 128 | |
| 116 | 129 | def add_system_aubio(ext): |
| 117 | 130 | # 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) | |
| 119 | 133 | 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 | + | |
| 138 | +def 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)] | |
| 121 | 143 | |
| 122 | 144 | class CleanGenerated(distutils.command.clean.clean): |
| 123 | 145 | 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) | |
| 148 | + | |
| 149 | +from distutils.command.build_ext import build_ext as _build_ext | |
| 150 | +class build_ext(_build_ext): | |
| 126 | 151 | |
| 127 | −class GenerateCommand(distutils.cmd.Command): | |
| 128 | − description = 'generate gen/gen-*.c files from ../src/aubio.h' | |
| 129 | − user_options = [ | |
| 152 | + user_options = _build_ext.user_options + [ | |
| 130 | 153 | # The format is (long option, short option, description). |
| 131 | 154 | ('enable-double', None, 'use HAVE_AUBIO_DOUBLE=1 (default: 0)'), |
| 132 | 155 | ] |
| 133 | 156 | |
| 134 | 157 | def initialize_options(self): |
| 158 | + _build_ext.initialize_options(self) | |
| 135 | 159 | self.enable_double = False |
| 136 | 160 | |
| 137 | 161 | def finalize_options(self): |
| 162 | + _build_ext.finalize_options(self) | |
| 138 | 163 | if self.enable_double: |
| 139 | 164 | self.announce( |
| 140 | 165 | 'will generate code for aubio compiled with HAVE_AUBIO_DOUBLE=1', |
| 141 | 166 | level=distutils.log.INFO) |
| 142 | 167 | |
| 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) | |
| @@ -1,206 +0,0 @@ | ||
| 1 | −#! /usr/bin/env python | |
| 2 | − | |
| 3 | −""" this file was written by Paul Brossier | |
| 4 | − it is released under the GNU/GPL license. | |
| 5 | −""" | |
| 6 | − | |
| 7 | −import sys | |
| 8 | −#from aubio.task import * | |
| 9 | − | |
| 10 | −usage = "usage: %s [options] -i soundfile" % sys.argv[0] | |
| 11 | −usage += "\n help: %s -h" % sys.argv[0] | |
| 12 | − | |
| 13 | −def parse_args(): | |
| 14 | − from optparse import OptionParser | |
| 15 | − parser = OptionParser(usage=usage) | |
| 16 | − parser.add_option("-i", "--input", action = "store", dest = "source_file", | |
| 17 | − help="input sound file to analyse", metavar = "<source_file>") | |
| 18 | − parser.add_option("-O","--onset-method", | |
| 19 | − action="store", dest="onset_method", default='default', | |
| 20 | − metavar = "<onset_method>", | |
| 21 | − help="onset detection method [default=default] \ | |
| 22 | − complexdomain|hfc|phase|specdiff|energy|kl|mkl") | |
| 23 | − # cutting methods | |
| 24 | − parser.add_option("-b","--beat", | |
| 25 | − action="store_true", dest="beat", default=False, | |
| 26 | − help="use beat locations") | |
| 27 | − """ | |
| 28 | − parser.add_option("-S","--silencecut", | |
| 29 | − action="store_true", dest="silencecut", default=False, | |
| 30 | − help="use silence locations") | |
| 31 | − parser.add_option("-s","--silence", | |
| 32 | − metavar = "<value>", | |
| 33 | − action="store", dest="silence", default=-70, | |
| 34 | − help="silence threshold [default=-70]") | |
| 35 | − """ | |
| 36 | − # algorithm parameters | |
| 37 | − parser.add_option("-r", "--samplerate", | |
| 38 | − metavar = "<freq>", type='int', | |
| 39 | − action="store", dest="samplerate", default=0, | |
| 40 | − help="samplerate at which the file should be represented") | |
| 41 | − parser.add_option("-B","--bufsize", | |
| 42 | − action="store", dest="bufsize", default=512, | |
| 43 | − metavar = "<size>", type='int', | |
| 44 | − help="buffer size [default=512]") | |
| 45 | − parser.add_option("-H","--hopsize", | |
| 46 | − metavar = "<size>", type='int', | |
| 47 | − action="store", dest="hopsize", default=256, | |
| 48 | − help="overlap size [default=256]") | |
| 49 | − parser.add_option("-t","--onset-threshold", | |
| 50 | − metavar = "<value>", type="float", | |
| 51 | − action="store", dest="threshold", default=0.3, | |
| 52 | − help="onset peak picking threshold [default=0.3]") | |
| 53 | − parser.add_option("-c","--cut", | |
| 54 | − action="store_true", dest="cut", default=False, | |
| 55 | − help="cut input sound file at detected labels \ | |
| 56 | − best used with option -L") | |
| 57 | − | |
| 58 | − # minioi | |
| 59 | − parser.add_option("-M","--minioi", | |
| 60 | − metavar = "<value>", type='string', | |
| 61 | − action="store", dest="minioi", default="12ms", | |
| 62 | − help="minimum inter onset interval [default=12ms]") | |
| 63 | − | |
| 64 | − """ | |
| 65 | − parser.add_option("-D","--delay", | |
| 66 | − action = "store", dest = "delay", type = "float", | |
| 67 | − metavar = "<seconds>", default=0, | |
| 68 | − help="number of seconds to take back [default=system]\ | |
| 69 | − default system delay is 3*hopsize/samplerate") | |
| 70 | − parser.add_option("-C","--dcthreshold", | |
| 71 | − metavar = "<value>", | |
| 72 | − action="store", dest="dcthreshold", default=1., | |
| 73 | − help="onset peak picking DC component [default=1.]") | |
| 74 | − parser.add_option("-L","--localmin", | |
| 75 | − action="store_true", dest="localmin", default=False, | |
| 76 | − help="use local minima after peak detection") | |
| 77 | − parser.add_option("-d","--derivate", | |
| 78 | − action="store_true", dest="derivate", default=False, | |
| 79 | − help="derivate onset detection function") | |
| 80 | − parser.add_option("-z","--zerocross", | |
| 81 | − metavar = "<value>", | |
| 82 | − action="store", dest="zerothres", default=0.008, | |
| 83 | − help="zero-crossing threshold for slicing [default=0.00008]") | |
| 84 | − """ | |
| 85 | − # plotting functions | |
| 86 | − """ | |
| 87 | − parser.add_option("-p","--plot", | |
| 88 | − action="store_true", dest="plot", default=False, | |
| 89 | − help="draw plot") | |
| 90 | − parser.add_option("-x","--xsize", | |
| 91 | − metavar = "<size>", | |
| 92 | − action="store", dest="xsize", default=1., | |
| 93 | − type='float', help="define xsize for plot") | |
| 94 | − parser.add_option("-y","--ysize", | |
| 95 | − metavar = "<size>", | |
| 96 | − action="store", dest="ysize", default=1., | |
| 97 | − type='float', help="define ysize for plot") | |
| 98 | − parser.add_option("-f","--function", | |
| 99 | − action="store_true", dest="func", default=False, | |
| 100 | − help="print detection function") | |
| 101 | − parser.add_option("-n","--no-onsets", | |
| 102 | − action="store_true", dest="nplot", default=False, | |
| 103 | − help="do not plot detected onsets") | |
| 104 | − parser.add_option("-O","--outplot", | |
| 105 | − metavar = "<output_image>", | |
| 106 | − action="store", dest="outplot", default=None, | |
| 107 | − help="save plot to output.{ps,png}") | |
| 108 | − parser.add_option("-F","--spectrogram", | |
| 109 | − action="store_true", dest="spectro", default=False, | |
| 110 | − help="add spectrogram to the plot") | |
| 111 | − """ | |
| 112 | − parser.add_option("-o","--output", type = str, | |
| 113 | − metavar = "<outputdir>", | |
| 114 | − action="store", dest="output_directory", default=None, | |
| 115 | − help="specify path where slices of the original file should be created") | |
| 116 | − parser.add_option("--cut-until-nsamples", type = int, | |
| 117 | − metavar = "<samples>", | |
| 118 | − action = "store", dest = "cut_until_nsamples", default = None, | |
| 119 | − help="how many extra samples should be added at the end of each slice") | |
| 120 | − parser.add_option("--cut-until-nslices", type = int, | |
| 121 | − metavar = "<slices>", | |
| 122 | − action = "store", dest = "cut_until_nslices", default = None, | |
| 123 | − help="how many extra slices should be added at the end of each slice") | |
| 124 | − | |
| 125 | − parser.add_option("-v","--verbose", | |
| 126 | − action="store_true", dest="verbose", default=True, | |
| 127 | − help="make lots of noise [default]") | |
| 128 | − parser.add_option("-q","--quiet", | |
| 129 | − action="store_false", dest="verbose", default=True, | |
| 130 | − help="be quiet") | |
| 131 | − (options, args) = parser.parse_args() | |
| 132 | − if not options.source_file: | |
| 133 | − import os.path | |
| 134 | − if len(args) == 1: | |
| 135 | − options.source_file = args[0] | |
| 136 | − else: | |
| 137 | − print ("no file name given\n" + usage) | |
| 138 | − sys.exit(1) | |
| 139 | − return options, args | |
| 140 | − | |
| 141 | −if __name__ == '__main__': | |
| 142 | − options, args = parse_args() | |
| 143 | − | |
| 144 | − hopsize = options.hopsize | |
| 145 | − bufsize = options.bufsize | |
| 146 | − samplerate = options.samplerate | |
| 147 | − source_file = options.source_file | |
| 148 | − | |
| 149 | − from aubio import onset, tempo, source, sink | |
| 150 | − | |
| 151 | − s = source(source_file, samplerate, hopsize) | |
| 152 | − if samplerate == 0: samplerate = s.get_samplerate() | |
| 153 | − | |
| 154 | − if options.beat: | |
| 155 | − o = tempo(options.onset_method, bufsize, hopsize) | |
| 156 | − else: | |
| 157 | − o = onset(options.onset_method, bufsize, hopsize) | |
| 158 | − if options.minioi: | |
| 159 | − if options.minioi.endswith('ms'): | |
| 160 | − o.set_minioi_ms(int(options.minioi[:-2])) | |
| 161 | − elif options.minioi.endswith('s'): | |
| 162 | − o.set_minioi_s(int(options.minioi[:-1])) | |
| 163 | − else: | |
| 164 | − o.set_minioi(int(options.minioi)) | |
| 165 | − o.set_threshold(options.threshold) | |
| 166 | − | |
| 167 | − timestamps = [] | |
| 168 | − total_frames = 0 | |
| 169 | − # analyze pass | |
| 170 | − while True: | |
| 171 | − samples, read = s() | |
| 172 | − if o(samples): | |
| 173 | − timestamps.append (o.get_last()) | |
| 174 | − if options.verbose: print ("%.4f" % o.get_last_s()) | |
| 175 | − total_frames += read | |
| 176 | − if read < hopsize: break | |
| 177 | − del s | |
| 178 | − # print some info | |
| 179 | − nstamps = len(timestamps) | |
| 180 | − duration = float (total_frames) / float(samplerate) | |
| 181 | − info = 'found %(nstamps)d timestamps in %(source_file)s' % locals() | |
| 182 | − info += ' (total %(duration).2fs at %(samplerate)dHz)\n' % locals() | |
| 183 | − sys.stderr.write(info) | |
| 184 | − | |
| 185 | − # cutting pass | |
| 186 | − if options.cut and nstamps > 0: | |
| 187 | − # generate output files | |
| 188 | − from aubio.slicing import slice_source_at_stamps | |
| 189 | − timestamps_end = None | |
| 190 | − if options.cut_until_nslices and options.cut_until_nsamples: | |
| 191 | − print ("warning: using cut_until_nslices, but cut_until_nsamples is set") | |
| 192 | − if options.cut_until_nsamples: | |
| 193 | − timestamps_end = [t + options.cut_until_nsamples for t in timestamps[1:]] | |
| 194 | − timestamps_end += [ 1e120 ] | |
| 195 | − if options.cut_until_nslices: | |
| 196 | − timestamps_end = [t for t in timestamps[1 + options.cut_until_nslices:]] | |
| 197 | − timestamps_end += [ 1e120 ] * (options.cut_until_nslices + 1) | |
| 198 | − slice_source_at_stamps(source_file, timestamps, timestamps_end = timestamps_end, | |
| 199 | − output_dir = options.output_directory, | |
| 200 | − samplerate = samplerate) | |
| 201 | − | |
| 202 | − # print some info | |
| 203 | − duration = float (total_frames) / float(samplerate) | |
| 204 | − info = 'created %(nstamps)d slices from %(source_file)s' % locals() | |
| 205 | − info += ' (total %(duration).2fs at %(samplerate)dHz)\n' % locals() | |
| 206 | − sys.stderr.write(info) | |
| @@ -0,0 +1 @@ | ||
| 1 | + | |
| @@ -24,7 +24,7 @@ import sys · 23 unmodified lines | ||
| 24 | 24 | import time |
| 25 | 25 | import os.path |
| 26 | 26 | import numpy |
| 27 | −from utils import array_from_text_file, array_from_yaml_file | |
| 27 | +from .utils import array_from_text_file, array_from_yaml_file | |
| 28 | 28 | from aubio import source, pitch, freqtomidi |
| 29 | 29 | |
| 30 | 30 | start = time.time() |
| @@ -9,6 +9,11 @@ class aubiomodule_test_case(TestCase): · 8 unmodified lines | ||
| 9 | 9 | """ try importing aubio """ |
| 10 | 10 | import aubio |
| 11 | 11 | |
| 12 | + def test_version(self): | |
| 13 | + """ test aubio.version """ | |
| 14 | + import aubio | |
| 15 | + self.assertEqual('0', aubio.version[0]) | |
| 16 | + | |
| 12 | 17 | if __name__ == '__main__': |
| 13 | 18 | main() |
| 14 | 19 | |
| @@ -0,0 +1,34 @@ | ||
| 1 | +#! /usr/bin/env python | |
| 2 | + | |
| 3 | +import aubio.cmd | |
| 4 | +from nose2 import main | |
| 5 | +from numpy.testing import TestCase | |
| 6 | + | |
| 7 | +class aubio_cmd(TestCase): | |
| 8 | + | |
| 9 | + def setUp(self): | |
| 10 | + self.a_parser = aubio.cmd.aubio_parser() | |
| 11 | + | |
| 12 | + def test_default_creation(self): | |
| 13 | + try: | |
| 14 | + assert self.a_parser.parse_args(['-V']).show_version | |
| 15 | + except SystemExit: | |
| 16 | + url = 'https://bugs.python.org/issue9253' | |
| 17 | + self.skipTest('subcommand became optional in py3, see %s' % url) | |
| 18 | + | |
| 19 | +class aubio_cmd_utils(TestCase): | |
| 20 | + | |
| 21 | + def test_samples2seconds(self): | |
| 22 | + self.assertEqual(aubio.cmd.samples2seconds(3200, 32000), | |
| 23 | + "0.100000\t") | |
| 24 | + | |
| 25 | + def test_samples2milliseconds(self): | |
| 26 | + self.assertEqual(aubio.cmd.samples2milliseconds(3200, 32000), | |
| 27 | + "100.000000\t") | |
| 28 | + | |
| 29 | + def test_samples2samples(self): | |
| 30 | + self.assertEqual(aubio.cmd.samples2samples(3200, 32000), | |
| 31 | + "3200\t") | |
| 32 | + | |
| 33 | +if __name__ == '__main__': | |
| 34 | + main() | |
| @@ -0,0 +1,16 @@ | ||
| 1 | +#! /usr/bin/env python | |
| 2 | + | |
| 3 | +import aubio.cut | |
| 4 | +from nose2 import main | |
| 5 | +from numpy.testing import TestCase | |
| 6 | + | |
| 7 | +class aubio_cut(TestCase): | |
| 8 | + | |
| 9 | + def setUp(self): | |
| 10 | + self.a_parser = aubio.cut.aubio_cut_parser() | |
| 11 | + | |
| 12 | + def test_default_creation(self): | |
| 13 | + assert self.a_parser.parse_args(['-v']).verbose | |
| 14 | + | |
| 15 | +if __name__ == '__main__': | |
| 16 | + main() | |
| @@ -0,0 +1,68 @@ | ||
| 1 | +#! /usr/bin/env python | |
| 2 | + | |
| 3 | + | |
| 4 | +import numpy as np | |
| 5 | +from numpy.testing import TestCase, assert_almost_equal | |
| 6 | +import aubio | |
| 7 | + | |
| 8 | +precomputed_arange = [ 9.89949512, -6.44232273, 0., -0.67345482, 0., | |
| 9 | + -0.20090288, 0., -0.05070186] | |
| 10 | + | |
| 11 | +precomputed_some_ones = [ 4.28539848, 0.2469689, -0.14625292, -0.58121818, | |
| 12 | + -0.83483052, -0.75921834, -0.35168475, 0.24087936, | |
| 13 | + 0.78539824, 1.06532764, 0.97632152, 0.57164496, 0.03688532, | |
| 14 | + -0.39446154, -0.54619485, -0.37771079] | |
| 15 | + | |
| 16 | +class aubio_dct(TestCase): | |
| 17 | + | |
| 18 | + def test_init(self): | |
| 19 | + """ test that aubio.dct() is created with expected size """ | |
| 20 | + a_dct = aubio.dct() | |
| 21 | + self.assertEqual(a_dct.size, 1024) | |
| 22 | + | |
| 23 | + def test_arange(self): | |
| 24 | + """ test that dct(arange(8)) is computed correctly | |
| 25 | + | |
| 26 | + >>> from scipy.fftpack import dct | |
| 27 | + >>> a_in = np.arange(8).astype(aubio.float_type) | |
| 28 | + >>> precomputed = dct(a_in, norm='ortho') | |
| 29 | + """ | |
| 30 | + N = len(precomputed_arange) | |
| 31 | + a_dct = aubio.dct(8) | |
| 32 | + a_in = np.arange(8).astype(aubio.float_type) | |
| 33 | + a_expected = aubio.fvec(precomputed_arange) | |
| 34 | + assert_almost_equal(a_dct(a_in), a_expected, decimal=5) | |
| 35 | + | |
| 36 | + def test_some_ones(self): | |
| 37 | + """ test that dct(somevector) is computed correctly """ | |
| 38 | + a_dct = aubio.dct(16) | |
| 39 | + a_in = np.ones(16).astype(aubio.float_type) | |
| 40 | + a_in[1] = 0 | |
| 41 | + a_in[3] = np.pi | |
| 42 | + a_expected = aubio.fvec(precomputed_some_ones) | |
| 43 | + assert_almost_equal(a_dct(a_in), a_expected, decimal=6) | |
| 44 | + | |
| 45 | + def test_reconstruction(self): | |
| 46 | + """ test that some_ones vector can be recontructed """ | |
| 47 | + a_dct = aubio.dct(16) | |
| 48 | + a_in = np.ones(16).astype(aubio.float_type) | |
| 49 | + a_in[1] = 0 | |
| 50 | + a_in[3] = np.pi | |
| 51 | + a_dct_in = a_dct(a_in) | |
| 52 | + a_dct_reconstructed = a_dct.rdo(a_dct_in) | |
| 53 | + assert_almost_equal(a_dct_reconstructed, a_in, decimal=6) | |
| 54 | + | |
| 55 | + def test_negative_size(self): | |
| 56 | + """ test that creation fails with a negative size """ | |
| 57 | + with self.assertRaises(ValueError): | |
| 58 | + aubio.dct(-1) | |
| 59 | + | |
| 60 | + def test_wrong_size(self): | |
| 61 | + """ test that creation fails with a non power-of-two size """ | |
| 62 | + # supports for non 2** fft sizes only when compiled with fftw3 | |
| 63 | + size = 13 | |
| 64 | + try: | |
| 65 | + with self.assertRaises(RuntimeError): | |
| 66 | + aubio.dct(size) | |
| 67 | + except AssertionError: | |
| 68 | + self.skipTest('creating aubio.dct with size %d did not fail' % size) | |
| @@ -33,7 +33,14 @@ class aubio_fft_test_case(TestCase): · 32 unmodified lines | ||
| 33 | 33 | f = fft (win_s) |
| 34 | 34 | fftgrain = f (timegrain) |
| 35 | 35 | assert_equal ( fftgrain.norm, 0 ) |
| 36 | − assert_equal ( fftgrain.phas, 0 ) | |
| 36 | + try: | |
| 37 | + assert_equal ( fftgrain.phas, 0 ) | |
| 38 | + except AssertionError: | |
| 39 | + assert_equal (fftgrain.phas[fftgrain.phas > 0], +pi) | |
| 40 | + assert_equal (fftgrain.phas[fftgrain.phas < 0], -pi) | |
| 41 | + assert_equal (np.abs(fftgrain.phas[np.abs(fftgrain.phas) != pi]), 0) | |
| 42 | + self.skipTest('fft(fvec(%d)).phas != +0, ' % win_s \ | |
| 43 | + + 'This is expected when using fftw3 on powerpc.') | |
| 37 | 44 | |
| 38 | 45 | def test_impulse(self): |
| 39 | 46 | """ check the transform of one impulse at a random place """ |
| @@ -135,6 +142,37 @@ class aubio_fft_test_case(TestCase): · 95 unmodified lines | ||
| 135 | 142 | assert_almost_equal ( r[0], impulse, decimal = 6) |
| 136 | 143 | assert_almost_equal ( r[1:], 0) |
| 137 | 144 | |
| 145 | +class aubio_fft_odd_sizes(TestCase): | |
| 146 | + | |
| 147 | + def test_reconstruct_with_odd_size(self): | |
| 148 | + win_s = 29 | |
| 149 | + self.recontruct(win_s, 'odd sizes not supported') | |
| 150 | + | |
| 151 | + def test_reconstruct_with_radix15(self): | |
| 152 | + win_s = 2 ** 4 * 15 | |
| 153 | + self.recontruct(win_s, 'radix 15 supported') | |
| 154 | + | |
| 155 | + def test_reconstruct_with_radix5(self): | |
| 156 | + win_s = 2 ** 4 * 5 | |
| 157 | + self.recontruct(win_s, 'radix 5 supported') | |
| 158 | + | |
| 159 | + def test_reconstruct_with_radix3(self): | |
| 160 | + win_s = 2 ** 4 * 3 | |
| 161 | + self.recontruct(win_s, 'radix 3 supported') | |
| 162 | + | |
| 163 | + def recontruct(self, win_s, skipMessage): | |
| 164 | + try: | |
| 165 | + f = fft(win_s) | |
| 166 | + except RuntimeError: | |
| 167 | + self.skipTest(skipMessage) | |
| 168 | + input_signal = fvec(win_s) | |
| 169 | + input_signal[win_s//2] = 1 | |
| 170 | + c = f(input_signal) | |
| 171 | + output_signal = f.rdo(c) | |
| 172 | + assert_almost_equal(input_signal, output_signal) | |
| 173 | + | |
| 174 | +class aubio_fft_wrong_params(TestCase): | |
| 175 | + | |
| 138 | 176 | def test_large_input_timegrain(self): |
| 139 | 177 | win_s = 1024 |
| 140 | 178 | f = fft(win_s) |
| @@ -163,22 +201,11 @@ class aubio_fft_test_case(TestCase): · 22 unmodified lines | ||
| 163 | 201 | with self.assertRaises(ValueError): |
| 164 | 202 | f.rdo(s) |
| 165 | 203 | |
| 166 | −class aubio_fft_wrong_params(TestCase): | |
| 167 | − | |
| 168 | 204 | def test_wrong_buf_size(self): |
| 169 | 205 | win_s = -1 |
| 170 | 206 | with self.assertRaises(ValueError): |
| 171 | 207 | fft(win_s) |
| 172 | 208 | |
| 173 | − def test_buf_size_not_power_of_two(self): | |
| 174 | − # when compiled with fftw3, aubio supports non power of two fft sizes | |
| 175 | − win_s = 320 | |
| 176 | − try: | |
| 177 | − with self.assertRaises(RuntimeError): | |
| 178 | − fft(win_s) | |
| 179 | − except AssertionError: | |
| 180 | − self.skipTest('creating aubio.fft with size %d did not fail' % win_s) | |
| 181 | − | |
| 182 | 209 | def test_buf_size_too_small(self): |
| 183 | 210 | win_s = 1 |
| 184 | 211 | with self.assertRaises(RuntimeError): |
| @@ -3,7 +3,7 @@ · 2 unmodified lines | ||
| 3 | 3 | from unittest import main |
| 4 | 4 | from numpy.testing import TestCase, assert_equal, assert_almost_equal |
| 5 | 5 | from aubio import fvec, digital_filter |
| 6 | −from utils import array_from_text_file | |
| 6 | +from .utils import array_from_text_file | |
| 7 | 7 | |
| 8 | 8 | class aubio_filter_test_case(TestCase): |
| 9 | 9 | |
| @@ -77,6 +77,16 @@ class aubio_filter_test_case(TestCase): · 67 unmodified lines | ||
| 77 | 77 | with self.assertRaises(ValueError): |
| 78 | 78 | f.set_biquad(0., 0., 0, 0., 0.) |
| 79 | 79 | |
| 80 | + def test_all_available_presets(self): | |
| 81 | + f = digital_filter(7) | |
| 82 | + for sr in [8000, 11025, 16000, 22050, 24000, 32000, | |
| 83 | + 44100, 48000, 88200, 96000, 192000]: | |
| 84 | + f.set_a_weighting(sr) | |
| 85 | + f = digital_filter(5) | |
| 86 | + for sr in [8000, 11025, 16000, 22050, 24000, 32000, | |
| 87 | + 44100, 48000, 88200, 96000, 192000]: | |
| 88 | + f.set_c_weighting(sr) | |
| 89 | + | |
| 80 | 90 | class aubio_filter_wrong_params(TestCase): |
| 81 | 91 | |
| 82 | 92 | def test_negative_order(self): |
| @@ -1,11 +1,10 @@ | ||
| 1 | 1 | #! /usr/bin/env python |
| 2 | 2 | |
| 3 | −from unittest import main | |
| 4 | −from numpy.testing import TestCase | |
| 5 | −from numpy.testing import assert_equal, assert_almost_equal | |
| 6 | 3 | import numpy as np |
| 4 | +from numpy.testing import TestCase, assert_equal, assert_almost_equal | |
| 5 | + | |
| 7 | 6 | from aubio import cvec, filterbank, float_type |
| 8 | −from utils import array_from_text_file | |
| 7 | +from .utils import array_from_text_file | |
| 9 | 8 | |
| 10 | 9 | class aubio_filterbank_test_case(TestCase): |
| 11 | 10 | |
| @@ -62,6 +61,13 @@ class aubio_filterbank_test_case(TestCase): · 50 unmodified lines | ||
| 62 | 61 | f.set_mel_coeffs_slaney(16000) |
| 63 | 62 | assert_almost_equal ( expected, f.get_coeffs() ) |
| 64 | 63 | |
| 64 | + def test_mfcc_coeffs_get_coeffs(self): | |
| 65 | + f = filterbank(40, 512) | |
| 66 | + coeffs = f.get_coeffs() | |
| 67 | + self.assertIsInstance(coeffs, np.ndarray) | |
| 68 | + assert_equal (coeffs, 0) | |
| 69 | + assert_equal (np.shape(coeffs), (40, 512 / 2 + 1)) | |
| 70 | + | |
| 65 | 71 | class aubio_filterbank_wrong_values(TestCase): |
| 66 | 72 | |
| 67 | 73 | def test_negative_window(self): |
| @@ -81,4 +87,5 @@ class aubio_filterbank_wrong_values(TestCase): · 13 unmodified lines | ||
| 81 | 87 | f(cvec(256)) |
| 82 | 88 | |
| 83 | 89 | if __name__ == '__main__': |
| 84 | − main() | |
| 90 | + import nose2 | |
| 91 | + nose2.main() | |
| @@ -1,10 +1,12 @@ | ||
| 1 | 1 | #! /usr/bin/env python |
| 2 | 2 | |
| 3 | −from unittest import main | |
| 4 | −from numpy.testing import TestCase | |
| 5 | −from numpy.testing import assert_equal, assert_almost_equal | |
| 6 | −from numpy import array, shape | |
| 7 | −from aubio import cvec, filterbank, float_type | |
| 3 | +import numpy as np | |
| 4 | +from numpy.testing import TestCase, assert_equal, assert_almost_equal | |
| 5 | + | |
| 6 | +from aubio import fvec, cvec, filterbank, float_type | |
| 7 | + | |
| 8 | +import warnings | |
| 9 | +warnings.filterwarnings('ignore', category=UserWarning, append=True) | |
| 8 | 10 | |
| 9 | 11 | class aubio_filterbank_mel_test_case(TestCase): |
| 10 | 12 | |
| @@ -12,38 +14,159 @@ class aubio_filterbank_mel_test_case(TestCase): · 1 unmodified line | ||
| 12 | 14 | f = filterbank(40, 512) |
| 13 | 15 | f.set_mel_coeffs_slaney(16000) |
| 14 | 16 | a = f.get_coeffs() |
| 15 | − assert_equal(shape (a), (40, 512/2 + 1) ) | |
| 17 | + assert_equal(np.shape (a), (40, 512/2 + 1) ) | |
| 16 | 18 | |
| 17 | 19 | def test_other_slaney(self): |
| 18 | 20 | f = filterbank(40, 512*2) |
| 19 | 21 | f.set_mel_coeffs_slaney(44100) |
| 20 | − _ = f.get_coeffs() | |
| 22 | + self.assertIsInstance(f.get_coeffs(), np.ndarray) | |
| 21 | 23 | #print "sum is", sum(sum(a)) |
| 22 | 24 | for win_s in [256, 512, 1024, 2048, 4096]: |
| 23 | 25 | f = filterbank(40, win_s) |
| 24 | 26 | f.set_mel_coeffs_slaney(32000) |
| 25 | − _ = f.get_coeffs() | |
| 26 | 27 | #print "sum is", sum(sum(a)) |
| 28 | + self.assertIsInstance(f.get_coeffs(), np.ndarray) | |
| 27 | 29 | |
| 28 | 30 | def test_triangle_freqs_zeros(self): |
| 29 | 31 | f = filterbank(9, 1024) |
| 30 | 32 | freq_list = [40, 80, 200, 400, 800, 1600, 3200, 6400, 12800, 15000, 24000] |
| 31 | − freqs = array(freq_list, dtype = float_type) | |
| 33 | + freqs = np.array(freq_list, dtype = float_type) | |
| 32 | 34 | f.set_triangle_bands(freqs, 48000) |
| 33 | − _ = f.get_coeffs().T | |
| 34 | 35 | assert_equal ( f(cvec(1024)), 0) |
| 36 | + self.assertIsInstance(f.get_coeffs(), np.ndarray) | |
| 35 | 37 | |
| 36 | 38 | def test_triangle_freqs_ones(self): |
| 37 | 39 | f = filterbank(9, 1024) |
| 38 | 40 | freq_list = [40, 80, 200, 400, 800, 1600, 3200, 6400, 12800, 15000, 24000] |
| 39 | − freqs = array(freq_list, dtype = float_type) | |
| 41 | + freqs = np.array(freq_list, dtype = float_type) | |
| 40 | 42 | f.set_triangle_bands(freqs, 48000) |
| 41 | − _ = f.get_coeffs().T | |
| 43 | + self.assertIsInstance(f.get_coeffs(), np.ndarray) | |
| 42 | 44 | spec = cvec(1024) |
| 43 | 45 | spec.norm[:] = 1 |
| 44 | 46 | assert_almost_equal ( f(spec), |
| 45 | 47 | [ 0.02070313, 0.02138672, 0.02127604, 0.02135417, |
| 46 | 48 | 0.02133301, 0.02133301, 0.02133311, 0.02133334, 0.02133345]) |
| 47 | 49 | |
| 50 | + def test_triangle_freqs_with_zeros(self): | |
| 51 | + """make sure set_triangle_bands works when list starts with 0""" | |
| 52 | + freq_list = [0, 40, 80] | |
| 53 | + freqs = np.array(freq_list, dtype = float_type) | |
| 54 | + f = filterbank(len(freqs)-2, 1024) | |
| 55 | + f.set_triangle_bands(freqs, 48000) | |
| 56 | + assert_equal ( f(cvec(1024)), 0) | |
| 57 | + self.assertIsInstance(f.get_coeffs(), np.ndarray) | |
| 58 | + | |
| 59 | + def test_triangle_freqs_with_wrong_negative(self): | |
| 60 | + """make sure set_triangle_bands fails when list contains a negative""" | |
| 61 | + freq_list = [-10, 0, 80] | |
| 62 | + f = filterbank(len(freq_list)-2, 1024) | |
| 63 | + with self.assertRaises(ValueError): | |
| 64 | + f.set_triangle_bands(fvec(freq_list), 48000) | |
| 65 | + | |
| 66 | + def test_triangle_freqs_with_wrong_ordering(self): | |
| 67 | + """make sure set_triangle_bands fails when list not ordered""" | |
| 68 | + freq_list = [0, 80, 40] | |
| 69 | + f = filterbank(len(freq_list)-2, 1024) | |
| 70 | + with self.assertRaises(ValueError): | |
| 71 | + f.set_triangle_bands(fvec(freq_list), 48000) | |
| 72 | + | |
| 73 | + def test_triangle_freqs_with_large_freq(self): | |
| 74 | + """make sure set_triangle_bands warns when freq > nyquist""" | |
| 75 | + samplerate = 22050 | |
| 76 | + freq_list = [0, samplerate//4, samplerate // 2 + 1] | |
| 77 | + f = filterbank(len(freq_list)-2, 1024) | |
| 78 | + # TODO add assert_warns | |
| 79 | + f.set_triangle_bands(fvec(freq_list), samplerate) | |
| 80 | + | |
| 81 | + def test_triangle_freqs_with_not_enough_filters(self): | |
| 82 | + """make sure set_triangle_bands warns when not enough filters""" | |
| 83 | + samplerate = 22050 | |
| 84 | + freq_list = [0, 100, 1000, 4000, 8000, 10000] | |
| 85 | + f = filterbank(len(freq_list)-3, 1024) | |
| 86 | + # TODO add assert_warns | |
| 87 | + f.set_triangle_bands(fvec(freq_list), samplerate) | |
| 88 | + | |
| 89 | + def test_triangle_freqs_with_too_many_filters(self): | |
| 90 | + """make sure set_triangle_bands warns when too many filters""" | |
| 91 | + samplerate = 22050 | |
| 92 | + freq_list = [0, 100, 1000, 4000, 8000, 10000] | |
| 93 | + f = filterbank(len(freq_list)-1, 1024) | |
| 94 | + # TODO add assert_warns | |
| 95 | + f.set_triangle_bands(fvec(freq_list), samplerate) | |
| 96 | + | |
| 97 | + def test_triangle_freqs_with_double_value(self): | |
| 98 | + """make sure set_triangle_bands works with 2 duplicate freqs""" | |
| 99 | + samplerate = 22050 | |
| 100 | + freq_list = [0, 100, 1000, 4000, 4000, 4000, 10000] | |
| 101 | + f = filterbank(len(freq_list)-2, 1024) | |
| 102 | + # TODO add assert_warns | |
| 103 | + f.set_triangle_bands(fvec(freq_list), samplerate) | |
| 104 | + | |
| 105 | + def test_triangle_freqs_with_triple(self): | |
| 106 | + """make sure set_triangle_bands works with 3 duplicate freqs""" | |
| 107 | + samplerate = 22050 | |
| 108 | + freq_list = [0, 100, 1000, 4000, 4000, 4000, 10000] | |
| 109 | + f = filterbank(len(freq_list)-2, 1024) | |
| 110 | + # TODO add assert_warns | |
| 111 | + f.set_triangle_bands(fvec(freq_list), samplerate) | |
| 112 | + | |
| 113 | + def test_triangle_freqs_without_norm(self): | |
| 114 | + """make sure set_triangle_bands works without """ | |
| 115 | + samplerate = 22050 | |
| 116 | + freq_list = fvec([0, 100, 1000, 10000]) | |
| 117 | + f = filterbank(len(freq_list) - 2, 1024) | |
| 118 | + f.set_norm(0) | |
| 119 | + f.set_triangle_bands(freq_list, samplerate) | |
| 120 | + expected = f.get_coeffs() | |
| 121 | + f.set_norm(1) | |
| 122 | + f.set_triangle_bands(fvec(freq_list), samplerate) | |
| 123 | + assert_almost_equal(f.get_coeffs().T, | |
| 124 | + expected.T * 2. / (freq_list[2:] - freq_list[:-2])) | |
| 125 | + | |
| 126 | + def test_triangle_freqs_wrong_norm(self): | |
| 127 | + f = filterbank(10, 1024) | |
| 128 | + with self.assertRaises(ValueError): | |
| 129 | + f.set_norm(-1) | |
| 130 | + | |
| 131 | + def test_triangle_freqs_with_power(self): | |
| 132 | + f = filterbank(9, 1024) | |
| 133 | + freqs = fvec([40, 80, 200, 400, 800, 1600, 3200, 6400, 12800, 15000, | |
| 134 | + 24000]) | |
| 135 | + f.set_power(2) | |
| 136 | + f.set_triangle_bands(freqs, 48000) | |
| 137 | + spec = cvec(1024) | |
| 138 | + spec.norm[:] = .1 | |
| 139 | + expected = fvec([0.02070313, 0.02138672, 0.02127604, 0.02135417, | |
| 140 | + 0.02133301, 0.02133301, 0.02133311, 0.02133334, 0.02133345]) | |
| 141 | + expected /= 100. | |
| 142 | + assert_almost_equal(f(spec), expected) | |
| 143 | + | |
| 144 | + def test_mel_coeffs(self): | |
| 145 | + f = filterbank(40, 1024) | |
| 146 | + f.set_mel_coeffs(44100, 0, 44100 / 2) | |
| 147 | + | |
| 148 | + def test_zero_fmax(self): | |
| 149 | + f = filterbank(40, 1024) | |
| 150 | + f.set_mel_coeffs(44100, 0, 0) | |
| 151 | + | |
| 152 | + def test_wrong_mel_coeffs(self): | |
| 153 | + f = filterbank(40, 1024) | |
| 154 | + with self.assertRaises(ValueError): | |
| 155 | + f.set_mel_coeffs_slaney(0) | |
| 156 | + with self.assertRaises(ValueError): | |
| 157 | + f.set_mel_coeffs(44100, 0, -44100 / 2) | |
| 158 | + with self.assertRaises(ValueError): | |
| 159 | + f.set_mel_coeffs(44100, -0.1, 44100 / 2) | |
| 160 | + with self.assertRaises(ValueError): | |
| 161 | + f.set_mel_coeffs(-44100, 0.1, 44100 / 2) | |
| 162 | + with self.assertRaises(ValueError): | |
| 163 | + f.set_mel_coeffs_htk(-1, 0, 0) | |
| 164 | + | |
| 165 | + def test_mel_coeffs_htk(self): | |
| 166 | + f = filterbank(40, 1024) | |
| 167 | + f.set_mel_coeffs_htk(44100, 0, 44100 / 2) | |
| 168 | + | |
| 169 | + | |
| 48 | 170 | if __name__ == '__main__': |
| 49 | − main() | |
| 171 | + import nose2 | |
| 172 | + nose2.main() | |
| @@ -60,6 +60,14 @@ class aubio_fvec_wrong_values(TestCase): · 59 unmodified lines | ||
| 60 | 60 | self.assertRaises(IndexError, a.__getitem__, 3) |
| 61 | 61 | self.assertRaises(IndexError, a.__getitem__, 2) |
| 62 | 62 | |
| 63 | + def test_wrong_dimensions(self): | |
| 64 | + a = np.array([[[1, 2], [3, 4]]], dtype=float_type) | |
| 65 | + self.assertRaises(ValueError, fvec, a) | |
| 66 | + | |
| 67 | + def test_wrong_size(self): | |
| 68 | + a = np.ndarray([0,], dtype=float_type) | |
| 69 | + self.assertRaises(ValueError, fvec, a) | |
| 70 | + | |
| 63 | 71 | class aubio_wrong_fvec_input(TestCase): |
| 64 | 72 | """ uses min_removal to test PyAubio_IsValidVector """ |
| 65 | 73 | |
| @@ -0,0 +1,35 @@ | ||
| 1 | +#! /usr/bin/env python | |
| 2 | + | |
| 3 | +import numpy as np | |
| 4 | +from numpy.testing import TestCase, assert_equal | |
| 5 | +import aubio | |
| 6 | + | |
| 7 | +class aubio_shift_test_case(TestCase): | |
| 8 | + | |
| 9 | + def run_shift_ishift(self, n): | |
| 10 | + ramp = np.arange(n, dtype=aubio.float_type) | |
| 11 | + # construct expected output | |
| 12 | + # even length: [5. 6. 7. 8. 9. 0. 1. 2. 3. 4.] | |
| 13 | + # odd length: [4. 5. 6. 0. 1. 2. 3.] | |
| 14 | + half = n - n//2 | |
| 15 | + expected = np.concatenate([np.arange(half, n), np.arange(half)]) | |
| 16 | + # shift in place, returns modified copy | |
| 17 | + assert_equal(aubio.shift(ramp), expected) | |
| 18 | + # check input was changed as expected | |
| 19 | + assert_equal(ramp, expected) | |
| 20 | + # construct expected output | |
| 21 | + expected = np.arange(n) | |
| 22 | + # revert shift in place, returns modifed copy | |
| 23 | + assert_equal(aubio.ishift(ramp), expected) | |
| 24 | + # check input was shifted back | |
| 25 | + assert_equal(ramp, expected) | |
| 26 | + | |
| 27 | + def test_can_shift_fvec(self): | |
| 28 | + self.run_shift_ishift(10) | |
| 29 | + | |
| 30 | + def test_can_shift_fvec_odd(self): | |
| 31 | + self.run_shift_ishift(7) | |
| 32 | + | |
| 33 | +from unittest import main | |
| 34 | +if __name__ == '__main__': | |
| 35 | + main() | |
| @@ -0,0 +1,109 @@ | ||
| 1 | +#! /usr/bin/env python | |
| 2 | + | |
| 3 | +from unittest import main | |
| 4 | +from numpy.testing import TestCase | |
| 5 | +from numpy.testing import assert_equal, assert_almost_equal | |
| 6 | +import numpy as np | |
| 7 | +import aubio | |
| 8 | + | |
| 9 | +from aubio import hztomel, meltohz | |
| 10 | +from aubio import hztomel_htk, meltohz_htk | |
| 11 | + | |
| 12 | + | |
| 13 | +class aubio_hztomel_test_case(TestCase): | |
| 14 | + | |
| 15 | + def test_hztomel(self): | |
| 16 | + assert_equal(hztomel(0.), 0.) | |
| 17 | + assert_almost_equal(hztomel(400. / 3.), 2., decimal=5) | |
| 18 | + assert_almost_equal(hztomel(1000. / 3), 5.) | |
| 19 | + assert_equal(hztomel(200.), 3.) | |
| 20 | + assert_almost_equal(hztomel(1000.), 15) | |
| 21 | + assert_almost_equal(hztomel(6400), 42) | |
| 22 | + assert_almost_equal(hztomel(40960), 69) | |
| 23 | + | |
| 24 | + for m in np.linspace(0, 1000, 100): | |
| 25 | + assert_almost_equal(hztomel(meltohz(m)) - m, 0, decimal=3) | |
| 26 | + | |
| 27 | + def test_meltohz(self): | |
| 28 | + assert_equal(meltohz(0.), 0.) | |
| 29 | + assert_almost_equal(meltohz(2), 400. / 3., decimal=4) | |
| 30 | + assert_equal(meltohz(3.), 200.) | |
| 31 | + assert_almost_equal(meltohz(5), 1000. / 3., decimal=4) | |
| 32 | + assert_almost_equal(meltohz(15), 1000., decimal=4) | |
| 33 | + assert_almost_equal(meltohz(42), 6400., decimal=2) | |
| 34 | + assert_almost_equal(meltohz(69), 40960., decimal=1) | |
| 35 | + | |
| 36 | + for f in np.linspace(0, 20000, 1000): | |
| 37 | + assert_almost_equal(meltohz(hztomel(f)) - f, 0, decimal=1) | |
| 38 | + | |
| 39 | + def test_meltohz_negative(self): | |
| 40 | + # TODO add assert_warns | |
| 41 | + assert_equal(meltohz(-1), 0) | |
| 42 | + | |
| 43 | + def test_hztomel_negative(self): | |
| 44 | + # TODO add assert_warns | |
| 45 | + assert_equal(hztomel(-1), 0) | |
| 46 | + | |
| 47 | + | |
| 48 | +class aubio_hztomel_htk_test_case(TestCase): | |
| 49 | + | |
| 50 | + def test_meltohz(self): | |
| 51 | + assert_equal(meltohz(0, htk=True), 0) | |
| 52 | + assert_almost_equal(meltohz(2595, htk=True), 6300., decimal=1) | |
| 53 | + | |
| 54 | + def test_hztomel(self): | |
| 55 | + assert_equal(hztomel(0, htk=True), 0) | |
| 56 | + assert_almost_equal(hztomel(3428.7, htk=True), 2000., decimal=1) | |
| 57 | + assert_almost_equal(hztomel(6300, htk=True), 2595., decimal=1) | |
| 58 | + | |
| 59 | + def test_meltohz_negative(self): | |
| 60 | + # TODO add assert_warns | |
| 61 | + assert_equal(meltohz(-1, htk=True), 0) | |
| 62 | + assert_almost_equal(meltohz(2000, htk=True), 3428.7, decimal=1) | |
| 63 | + assert_almost_equal(meltohz(1000, htk=True), 1000., decimal=1) | |
| 64 | + | |
| 65 | + def test_hztomel_negative(self): | |
| 66 | + # TODO add assert_warns | |
| 67 | + assert_equal(hztomel(-1, htk=True), 0) | |
| 68 | + assert_almost_equal(hztomel(1000, htk=True), 1000., decimal=1) | |
| 69 | + | |
| 70 | + def test_hztomel_htk(self): | |
| 71 | + for f in np.linspace(0, 20000, 1000): | |
| 72 | + assert_almost_equal(meltohz_htk(hztomel_htk(f)) - f, 0, decimal=1) | |
| 73 | + for f in np.linspace(0, 20000, 1000): | |
| 74 | + assert_almost_equal(hztomel_htk(meltohz_htk(f)) - f, 0, decimal=1) | |
| 75 | + | |
| 76 | + | |
| 77 | +class aubio_hztomel_wrong_values(TestCase): | |
| 78 | + """ more tests to cover all branches """ | |
| 79 | + | |
| 80 | + def test_hztomel_wrong_values(self): | |
| 81 | + with self.assertRaises(TypeError): | |
| 82 | + hztomel('s') | |
| 83 | + | |
| 84 | + def test_meltohz_wrong_values(self): | |
| 85 | + with self.assertRaises(TypeError): | |
| 86 | + meltohz(bytes('ad')) | |
| 87 | + | |
| 88 | + def test_meltohz_no_arg(self): | |
| 89 | + with self.assertRaises(TypeError): | |
| 90 | + meltohz() | |
| 91 | + | |
| 92 | + def test_meltohz_htk_no_arg(self): | |
| 93 | + with self.assertRaises(TypeError): | |
| 94 | + meltohz_htk() | |
| 95 | + | |
| 96 | + def test_hztomel_htk_wrong_values(self): | |
| 97 | + with self.assertRaises(TypeError): | |
| 98 | + hztomel_htk('0') | |
| 99 | + | |
| 100 | + def test_hztomel_htk_false(self): | |
| 101 | + assert hztomel(120, htk=False) == hztomel(120) | |
| 102 | + | |
| 103 | + def test_meltohz_htk_false(self): | |
| 104 | + assert meltohz(12, htk=False) == meltohz(12) | |
| 105 | + | |
| 106 | + | |
| 107 | +if __name__ == '__main__': | |
| 108 | + from unittest import main | |
| 109 | + main() | |
| @@ -110,5 +110,42 @@ class aubio_mfcc_all_parameters(TestCase): · 109 unmodified lines | ||
| 110 | 110 | o(spec) |
| 111 | 111 | #print coeffs |
| 112 | 112 | |
| 113 | + | |
| 114 | +class aubio_mfcc_fb_params(TestCase): | |
| 115 | + | |
| 116 | + def test_set_scale(self): | |
| 117 | + buf_size, n_filters, n_coeffs, samplerate = 512, 20, 10, 16000 | |
| 118 | + m = mfcc(buf_size, n_filters, n_coeffs, samplerate) | |
| 119 | + m.set_scale(10.5) | |
| 120 | + assert m.get_scale() == 10.5 | |
| 121 | + m(cvec(buf_size)) | |
| 122 | + | |
| 123 | + def test_set_power(self): | |
| 124 | + buf_size, n_filters, n_coeffs, samplerate = 512, 20, 10, 16000 | |
| 125 | + m = mfcc(buf_size, n_filters, n_coeffs, samplerate) | |
| 126 | + m.set_power(2.5) | |
| 127 | + assert m.get_power() == 2.5 | |
| 128 | + m(cvec(buf_size)) | |
| 129 | + | |
| 130 | + def test_set_mel_coeffs(self): | |
| 131 | + buf_size, n_filters, n_coeffs, samplerate = 512, 20, 10, 16000 | |
| 132 | + m = mfcc(buf_size, n_filters, n_coeffs, samplerate) | |
| 133 | + m.set_mel_coeffs(0., samplerate/2.) | |
| 134 | + m(cvec(buf_size)) | |
| 135 | + | |
| 136 | + def test_set_mel_coeffs_htk(self): | |
| 137 | + buf_size, n_filters, n_coeffs, samplerate = 512, 20, 10, 16000 | |
| 138 | + m = mfcc(buf_size, n_filters, n_coeffs, samplerate) | |
| 139 | + m.set_mel_coeffs_htk(0., samplerate/2.) | |
| 140 | + m(cvec(buf_size)) | |
| 141 | + | |
| 142 | + def test_set_mel_coeffs_slaney(self): | |
| 143 | + buf_size, n_filters, n_coeffs, samplerate = 512, 40, 10, 16000 | |
| 144 | + m = mfcc(buf_size, n_filters, n_coeffs, samplerate) | |
| 145 | + m.set_mel_coeffs_slaney() | |
| 146 | + m(cvec(buf_size)) | |
| 147 | + assert m.get_power() == 1 | |
| 148 | + assert m.get_scale() == 1 | |
| 149 | + | |
| 113 | 150 | if __name__ == '__main__': |
| 114 | 151 | main() |
| @@ -2,6 +2,7 @@ · 1 unmodified line | ||
| 2 | 2 | # -*- coding: utf-8 -*- |
| 3 | 3 | |
| 4 | 4 | from aubio import midi2note |
| 5 | +from nose2.tools import params | |
| 5 | 6 | import unittest |
| 6 | 7 | |
| 7 | 8 | list_of_known_midis = ( |
| @@ -16,10 +17,10 @@ list_of_known_midis = ( · 8 unmodified lines | ||
| 16 | 17 | |
| 17 | 18 | class midi2note_good_values(unittest.TestCase): |
| 18 | 19 | |
| 19 | − def test_midi2note_known_values(self): | |
| 20 | + @params(*list_of_known_midis) | |
| 21 | + def test_midi2note_known_values(self, midi, note): | |
| 20 | 22 | " known values are correctly converted " |
| 21 | − for midi, note in list_of_known_midis: | |
| 22 | − self.assertEqual ( midi2note(midi), note ) | |
| 23 | + self.assertEqual ( midi2note(midi), note ) | |
| 23 | 24 | |
| 24 | 25 | class midi2note_wrong_values(unittest.TestCase): |
| 25 | 26 | |
| @@ -40,4 +41,5 @@ class midi2note_wrong_values(unittest.TestCase): · 14 unmodified lines | ||
| 40 | 41 | self.assertRaises(TypeError, midi2note, "a") |
| 41 | 42 | |
| 42 | 43 | if __name__ == '__main__': |
| 43 | − unittest.main() | |
| 44 | + import nose2 | |
| 45 | + nose2.main() | |
| @@ -3,7 +3,8 @@ · 2 unmodified lines | ||
| 3 | 3 | |
| 4 | 4 | from __future__ import unicode_literals |
| 5 | 5 | |
| 6 | −from aubio import note2midi, freq2note | |
| 6 | +from aubio import note2midi, freq2note, note2freq, float_type | |
| 7 | +from nose2.tools import params | |
| 7 | 8 | import unittest |
| 8 | 9 | |
| 9 | 10 | list_of_known_notes = ( |
| @@ -13,24 +14,57 @@ list_of_known_notes = ( · 3 unmodified lines | ||
| 13 | 14 | ( 'C3', 48 ), |
| 14 | 15 | ( 'B3', 59 ), |
| 15 | 16 | ( 'B#3', 60 ), |
| 17 | + ( 'C♯4', 61 ), | |
| 16 | 18 | ( 'A4', 69 ), |
| 17 | 19 | ( 'A#4', 70 ), |
| 20 | + ( 'A♯4', 70 ), | |
| 21 | + ( 'A\u266f4', 70 ), | |
| 18 | 22 | ( 'Bb4', 70 ), |
| 19 | 23 | ( 'B♭4', 70 ), |
| 24 | + ( 'B\u266d4', 70 ), | |
| 20 | 25 | ( 'G8', 115 ), |
| 21 | 26 | ( 'G♯8', 116 ), |
| 22 | 27 | ( 'G9', 127 ), |
| 23 | − ( 'G\udd2a2', 45 ), | |
| 24 | − ( 'B\ufffd2', 45 ), | |
| 25 | 28 | ( 'A♮2', 45 ), |
| 26 | 29 | ) |
| 27 | 30 | |
| 31 | +list_of_known_notes_with_unicode_issues = ( | |
| 32 | + ('C𝄪4', 62 ), | |
| 33 | + ('E𝄫4', 62 ), | |
| 34 | + ) | |
| 35 | + | |
| 36 | +list_of_unknown_notes = ( | |
| 37 | + ( 'G\udd2a2' ), | |
| 38 | + ( 'B\ufffd2' ), | |
| 39 | + ( 'B\u266e\u266e2' ), | |
| 40 | + ( 'B\u266f\u266d3' ), | |
| 41 | + ( 'B33' ), | |
| 42 | + ( 'C.3' ), | |
| 43 | + ( 'A' ), | |
| 44 | + ( '2' ), | |
| 45 | + ) | |
| 46 | + | |
| 28 | 47 | class note2midi_good_values(unittest.TestCase): |
| 29 | 48 | |
| 30 | − def test_note2midi_known_values(self): | |
| 49 | + @params(*list_of_known_notes) | |
| 50 | + def test_note2midi_known_values(self, note, midi): | |
| 31 | 51 | " known values are correctly converted " |
| 32 | − for note, midi in list_of_known_notes: | |
| 52 | + self.assertEqual ( note2midi(note), midi ) | |
| 53 | + | |
| 54 | + @params(*list_of_known_notes_with_unicode_issues) | |
| 55 | + def test_note2midi_known_values_with_unicode_issues(self, note, midi): | |
| 56 | + " known values are correctly converted, unless decoding is expected to fail" | |
| 57 | + try: | |
| 33 | 58 | self.assertEqual ( note2midi(note), midi ) |
| 59 | + except UnicodeEncodeError as e: | |
| 60 | + import sys | |
| 61 | + strfmt = "len(u'\\U0001D12A') != 1, excpected decoding failure | {:s} | {:s} {:s}" | |
| 62 | + strres = strfmt.format(e, sys.platform, sys.version) | |
| 63 | + # happens with: darwin 2.7.10, windows 2.7.12 | |
| 64 | + if len('\U0001D12A') != 1 and sys.version[0] == '2': | |
| 65 | + self.skipTest(strres + " | upgrade to Python 3 to fix") | |
| 66 | + else: | |
| 67 | + raise | |
| 34 | 68 | |
| 35 | 69 | class note2midi_wrong_values(unittest.TestCase): |
| 36 | 70 | |
| @@ -66,12 +100,38 @@ class note2midi_wrong_values(unittest.TestCase): · 29 unmodified lines | ||
| 66 | 100 | " fails when passed a non-string value " |
| 67 | 101 | self.assertRaises(TypeError, note2midi, 123) |
| 68 | 102 | |
| 103 | + def test_note2midi_wrong_data_too_long(self): | |
| 104 | + " fails when passed a note with a note name longer than expected" | |
| 105 | + self.assertRaises(ValueError, note2midi, 'CB+-3') | |
| 106 | + | |
| 107 | + @params(*list_of_unknown_notes) | |
| 108 | + def test_note2midi_unknown_values(self, note): | |
| 109 | + " unknown values throw out an error " | |
| 110 | + self.assertRaises(ValueError, note2midi, note) | |
| 69 | 111 | |
| 70 | 112 | class freq2note_simple_test(unittest.TestCase): |
| 71 | 113 | |
| 72 | − def test_freq2note(self): | |
| 114 | + def test_freq2note_above(self): | |
| 73 | 115 | " make sure freq2note(441) == A4 " |
| 74 | 116 | self.assertEqual("A4", freq2note(441)) |
| 75 | 117 | |
| 118 | + def test_freq2note_under(self): | |
| 119 | + " make sure freq2note(439) == A4 " | |
| 120 | + self.assertEqual("A4", freq2note(439)) | |
| 121 | + | |
| 122 | +class note2freq_simple_test(unittest.TestCase): | |
| 123 | + | |
| 124 | + def test_note2freq(self): | |
| 125 | + " make sure note2freq('A3') == 220" | |
| 126 | + self.assertEqual(220, note2freq("A3")) | |
| 127 | + | |
| 128 | + def test_note2freq_under(self): | |
| 129 | + " make sure note2freq(A4) == 440" | |
| 130 | + if float_type == 'float32': | |
| 131 | + self.assertEqual(440, note2freq("A4")) | |
| 132 | + else: | |
| 133 | + self.assertLess(abs(note2freq("A4")-440), 1.e-12) | |
| 134 | + | |
| 76 | 135 | if __name__ == '__main__': |
| 77 | − unittest.main() | |
| 136 | + import nose2 | |
| 137 | + nose2.main() | |
| @@ -0,0 +1,95 @@ | ||
| 1 | +#! /usr/bin/env python | |
| 2 | + | |
| 3 | +from unittest import main | |
| 4 | +from numpy.testing import TestCase, assert_equal, assert_almost_equal | |
| 5 | +from aubio import notes | |
| 6 | + | |
| 7 | +AUBIO_DEFAULT_NOTES_SILENCE = -70. | |
| 8 | +AUBIO_DEFAULT_NOTES_RELEASE_DROP = 10. | |
| 9 | +AUBIO_DEFAULT_NOTES_MINIOI_MS = 30. | |
| 10 | + | |
| 11 | +class aubio_notes_default(TestCase): | |
| 12 | + | |
| 13 | + def test_members(self): | |
| 14 | + o = notes() | |
| 15 | + assert_equal ([o.buf_size, o.hop_size, o.method, o.samplerate], | |
| 16 | + [1024,512,'default',44100]) | |
| 17 | + | |
| 18 | + | |
| 19 | +class aubio_notes_params(TestCase): | |
| 20 | + | |
| 21 | + samplerate = 44100 | |
| 22 | + | |
| 23 | + def setUp(self): | |
| 24 | + self.o = notes(samplerate = self.samplerate) | |
| 25 | + | |
| 26 | + def test_get_minioi_ms(self): | |
| 27 | + assert_equal (self.o.get_minioi_ms(), AUBIO_DEFAULT_NOTES_MINIOI_MS) | |
| 28 | + | |
| 29 | + def test_set_minioi_ms(self): | |
| 30 | + val = 40. | |
| 31 | + self.o.set_minioi_ms(val) | |
| 32 | + assert_almost_equal (self.o.get_minioi_ms(), val) | |
| 33 | + | |
| 34 | + def test_get_silence(self): | |
| 35 | + assert_equal (self.o.get_silence(), AUBIO_DEFAULT_NOTES_SILENCE) | |
| 36 | + | |
| 37 | + def test_set_silence(self): | |
| 38 | + val = -50 | |
| 39 | + self.o.set_silence(val) | |
| 40 | + assert_equal (self.o.get_silence(), val) | |
| 41 | + | |
| 42 | + def test_get_release_drop(self): | |
| 43 | + assert_equal (self.o.get_release_drop(), AUBIO_DEFAULT_NOTES_RELEASE_DROP) | |
| 44 | + | |
| 45 | + def test_set_release_drop(self): | |
| 46 | + val = 50 | |
| 47 | + self.o.set_release_drop(val) | |
| 48 | + assert_equal (self.o.get_release_drop(), val) | |
| 49 | + | |
| 50 | + def test_set_release_drop_wrong(self): | |
| 51 | + val = -10 | |
| 52 | + with self.assertRaises(ValueError): | |
| 53 | + self.o.set_release_drop(val) | |
| 54 | + | |
| 55 | +from .utils import list_all_sounds | |
| 56 | +list_of_sounds = list_all_sounds('sounds') | |
| 57 | + | |
| 58 | +class aubio_notes_sinewave(TestCase): | |
| 59 | + | |
| 60 | + def analyze_file(self, filepath, samplerate=0): | |
| 61 | + from aubio import source | |
| 62 | + import numpy as np | |
| 63 | + win_s = 512 # fft size | |
| 64 | + hop_s = 256 # hop size | |
| 65 | + | |
| 66 | + s = source(filepath, samplerate, hop_s) | |
| 67 | + samplerate = s.samplerate | |
| 68 | + | |
| 69 | + tolerance = 0.8 | |
| 70 | + | |
| 71 | + notes_o = notes("default", win_s, hop_s, samplerate) | |
| 72 | + total_frames = 0 | |
| 73 | + | |
| 74 | + results = [] | |
| 75 | + while True: | |
| 76 | + samples, read = s() | |
| 77 | + new_note = notes_o(samples) | |
| 78 | + if (new_note[0] != 0): | |
| 79 | + note_str = ' '.join(["%.2f" % i for i in new_note]) | |
| 80 | + results.append( [total_frames, np.copy(new_note)] ) | |
| 81 | + total_frames += read | |
| 82 | + if read < hop_s: break | |
| 83 | + return results | |
| 84 | + | |
| 85 | + def test_sinewave(self): | |
| 86 | + for filepath in list_of_sounds: | |
| 87 | + if '44100Hz_44100f_sine441.wav' in filepath: | |
| 88 | + results = self.analyze_file(filepath) | |
| 89 | + assert_equal (len(results), 1) | |
| 90 | + assert_equal (len(results[0]), 2) | |
| 91 | + assert_equal (results[0][0], 1280) | |
| 92 | + assert_equal (results[0][1], [69, 123, -1]) | |
| 93 | + | |
| 94 | +if __name__ == '__main__': | |
| 95 | + main() | |
| @@ -2,7 +2,7 @@ · 1 unmodified line | ||
| 2 | 2 | |
| 3 | 3 | from unittest import main |
| 4 | 4 | from numpy.testing import TestCase, assert_equal, assert_almost_equal |
| 5 | −from aubio import onset | |
| 5 | +from aubio import onset, fvec | |
| 6 | 6 | |
| 7 | 7 | class aubio_onset_default(TestCase): |
| 8 | 8 | |
| @@ -19,25 +19,25 @@ class aubio_onset_params(TestCase): · 10 unmodified lines | ||
| 19 | 19 | self.o = onset(samplerate = self.samplerate) |
| 20 | 20 | |
| 21 | 21 | def test_get_delay(self): |
| 22 | − assert_equal (self.o.get_delay(), int(4.3 * self.o.hop_size)) | |
| 22 | + self.assertGreater(self.o.get_delay(), 0) | |
| 23 | 23 | |
| 24 | 24 | def test_get_delay_s(self): |
| 25 | − assert_almost_equal (self.o.get_delay_s(), self.o.get_delay() / float(self.samplerate)) | |
| 25 | + self.assertGreater(self.o.get_delay_s(), 0.) | |
| 26 | 26 | |
| 27 | 27 | def test_get_delay_ms(self): |
| 28 | − assert_almost_equal (self.o.get_delay_ms(), self.o.get_delay() * 1000. / self.samplerate, 5) | |
| 28 | + self.assertGreater(self.o.get_delay_ms(), 0.) | |
| 29 | 29 | |
| 30 | 30 | def test_get_minioi(self): |
| 31 | − assert_almost_equal (self.o.get_minioi(), 0.02 * self.samplerate) | |
| 31 | + self.assertGreater(self.o.get_minioi(), 0) | |
| 32 | 32 | |
| 33 | 33 | def test_get_minioi_s(self): |
| 34 | − assert_almost_equal (self.o.get_minioi_s(), 0.02) | |
| 34 | + self.assertGreater(self.o.get_minioi_s(), 0.) | |
| 35 | 35 | |
| 36 | 36 | def test_get_minioi_ms(self): |
| 37 | − assert_equal (self.o.get_minioi_ms(), 20.) | |
| 37 | + self.assertGreater(self.o.get_minioi_ms(), 0.) | |
| 38 | 38 | |
| 39 | 39 | def test_get_threshold(self): |
| 40 | − assert_almost_equal (self.o.get_threshold(), 0.3) | |
| 40 | + self.assertGreater(self.o.get_threshold(), 0.) | |
| 41 | 41 | |
| 42 | 42 | def test_set_delay(self): |
| 43 | 43 | val = 256 |
| @@ -83,5 +83,37 @@ class aubio_onset_32000(aubio_onset_params): · 39 unmodified lines | ||
| 83 | 83 | class aubio_onset_8000(aubio_onset_params): |
| 84 | 84 | samplerate = 8000 |
| 85 | 85 | |
| 86 | +class aubio_onset_coverate(TestCase): | |
| 87 | + # extra tests to execute the C routines and improve coverage | |
| 88 | + | |
| 89 | + def test_all_methods(self): | |
| 90 | + for method in ['default', 'energy', 'hfc', 'complexdomain', 'complex', | |
| 91 | + 'phase', 'wphase', 'mkl', 'kl', 'specflux', 'specdiff', | |
| 92 | + 'old_default']: | |
| 93 | + o = onset(method=method, buf_size=512, hop_size=256) | |
| 94 | + o(fvec(256)) | |
| 95 | + | |
| 96 | + def test_get_methods(self): | |
| 97 | + o = onset(method='default', buf_size=512, hop_size=256) | |
| 98 | + | |
| 99 | + assert o.get_silence() == -70 | |
| 100 | + o.set_silence(-20) | |
| 101 | + assert_almost_equal(o.get_silence(), -20) | |
| 102 | + | |
| 103 | + assert o.get_compression() == 1 | |
| 104 | + o.set_compression(.99) | |
| 105 | + assert_almost_equal(o.get_compression(), .99) | |
| 106 | + | |
| 107 | + assert o.get_awhitening() == 0 | |
| 108 | + o.set_awhitening(1) | |
| 109 | + assert o.get_awhitening() == 1 | |
| 110 | + | |
| 111 | + o.get_last() | |
| 112 | + o.get_last_ms() | |
| 113 | + o.get_last_s() | |
| 114 | + o.get_descriptor() | |
| 115 | + o.get_thresholded_descriptor() | |
| 116 | + | |
| 117 | + | |
| 86 | 118 | if __name__ == '__main__': |
| 87 | 119 | main() |
| @@ -46,9 +46,25 @@ class aubio_pvoc_test_case(TestCase): · 45 unmodified lines | ||
| 46 | 46 | r = f.rdo(s) |
| 47 | 47 | assert_equal ( t, 0.) |
| 48 | 48 | assert_equal ( s.norm, 0.) |
| 49 | − assert_equal ( s.phas, 0.) | |
| 49 | + try: | |
| 50 | + assert_equal ( s.phas, 0 ) | |
| 51 | + except AssertionError: | |
| 52 | + assert_equal (s.phas[s.phas > 0], +np.pi) | |
| 53 | + assert_equal (s.phas[s.phas < 0], -np.pi) | |
| 54 | + assert_equal (np.abs(s.phas[np.abs(s.phas) != np.pi]), 0) | |
| 55 | + self.skipTest('pvoc(fvec(%d)).phas != +0, ' % win_s \ | |
| 56 | + + 'This is expected when using fftw3 on powerpc.') | |
| 50 | 57 | assert_equal ( r, 0.) |
| 51 | 58 | |
| 59 | + def test_no_overlap(self): | |
| 60 | + win_s, hop_s = 1024, 1024 | |
| 61 | + f = pvoc (win_s, hop_s) | |
| 62 | + t = fvec (hop_s) | |
| 63 | + for _ in range(4): | |
| 64 | + s = f(t) | |
| 65 | + r = f.rdo(s) | |
| 66 | + assert_equal ( t, 0.) | |
| 67 | + | |
| 52 | 68 | @params( |
| 53 | 69 | ( 256, 8), |
| 54 | 70 | ( 256, 4), |
| @@ -70,8 +70,8 @@ class aubio_pitch_Sinusoid(TestCase): · 69 unmodified lines | ||
| 70 | 70 | #print 'len(pitches), cut:', len(pitches), cut |
| 71 | 71 | #print 'median errors: ', median(errors), 'median pitches: ', median(pitches) |
| 72 | 72 | |
| 73 | −pitch_algorithms = [ "default", "yinfft", "yin", "schmitt", "mcomb", "fcomb" , "specacf" ] | |
| 74 | −pitch_algorithms = [ "default", "yinfft", "yin", "schmitt", "mcomb", "fcomb" ] | |
| 73 | +pitch_algorithms = [ "default", "yinfft", "yin", "yinfast", "schmitt", "mcomb", "fcomb" , "specacf" ] | |
| 74 | +pitch_algorithms = [ "default", "yinfft", "yin", "yinfast", "schmitt", "mcomb", "fcomb" ] | |
| 75 | 75 | |
| 76 | 76 | #freqs = [ 27.5, 55., 110., 220., 440., 880., 1760., 3520. ] |
| 77 | 77 | freqs = [ 110., 220., 440., 880., 1760., 3520. ] |
| @@ -4,7 +4,10 @@ from nose2 import main · 3 unmodified lines | ||
| 4 | 4 | from nose2.tools import params |
| 5 | 5 | from numpy.testing import TestCase |
| 6 | 6 | from aubio import fvec, source, sink |
| 7 | −from utils import list_all_sounds, get_tmp_sink_path, del_tmp_sink_path | |
| 7 | +from .utils import list_all_sounds, get_tmp_sink_path, del_tmp_sink_path | |
| 8 | + | |
| 9 | +import warnings | |
| 10 | +warnings.filterwarnings('ignore', category=UserWarning, append=True) | |
| 8 | 11 | |
| 9 | 12 | list_of_sounds = list_all_sounds('sounds') |
| 10 | 13 | samplerates = [0, 44100, 8000, 32000] |
| @@ -26,6 +29,26 @@ class aubio_sink_test_case(TestCase): · 15 unmodified lines | ||
| 26 | 29 | if not len(list_of_sounds): |
| 27 | 30 | self.skipTest('add some sound files in \'python/tests/sounds\'') |
| 28 | 31 | |
| 32 | + def test_wrong_filename(self): | |
| 33 | + with self.assertRaises(RuntimeError): | |
| 34 | + sink('') | |
| 35 | + | |
| 36 | + def test_wrong_samplerate(self): | |
| 37 | + with self.assertRaises(RuntimeError): | |
| 38 | + sink(get_tmp_sink_path(), -1) | |
| 39 | + | |
| 40 | + def test_wrong_samplerate_too_large(self): | |
| 41 | + with self.assertRaises(RuntimeError): | |
| 42 | + sink(get_tmp_sink_path(), 1536001, 2) | |
| 43 | + | |
| 44 | + def test_wrong_channels(self): | |
| 45 | + with self.assertRaises(RuntimeError): | |
| 46 | + sink(get_tmp_sink_path(), 44100, -1) | |
| 47 | + | |
| 48 | + def test_wrong_channels_too_large(self): | |
| 49 | + with self.assertRaises(RuntimeError): | |
| 50 | + sink(get_tmp_sink_path(), 44100, 202020) | |
| 51 | + | |
| 29 | 52 | def test_many_sinks(self): |
| 30 | 53 | from tempfile import mkdtemp |
| 31 | 54 | import os.path |
| @@ -93,5 +116,13 @@ class aubio_sink_test_case(TestCase): · 61 unmodified lines | ||
| 93 | 116 | g.close() |
| 94 | 117 | del_tmp_sink_path(sink_path) |
| 95 | 118 | |
| 119 | + def test_read_with(self): | |
| 120 | + samplerate = 44100 | |
| 121 | + sink_path = get_tmp_sink_path() | |
| 122 | + vec = fvec(128) | |
| 123 | + with sink(sink_path, samplerate) as g: | |
| 124 | + for _ in range(10): | |
| 125 | + g(vec, 128) | |
| 126 | + | |
| 96 | 127 | if __name__ == '__main__': |
| 97 | 128 | main() |
| @@ -3,8 +3,8 @@ · 2 unmodified lines | ||
| 3 | 3 | from unittest import main |
| 4 | 4 | from numpy.testing import TestCase, assert_equal |
| 5 | 5 | from aubio import slice_source_at_stamps |
| 6 | −from utils import count_files_in_directory, get_default_test_sound | |
| 7 | −from utils import count_samples_in_directory, count_samples_in_file | |
| 6 | +from .utils import count_files_in_directory, get_default_test_sound | |
| 7 | +from .utils import count_samples_in_directory, count_samples_in_file | |
| 8 | 8 | |
| 9 | 9 | import tempfile |
| 10 | 10 | import shutil |
| @@ -23,19 +23,27 @@ class aubio_slicing_test_case(TestCase): · 12 unmodified lines | ||
| 23 | 23 | |
| 24 | 24 | def test_slice_start_only_no_zero(self): |
| 25 | 25 | regions_start = [i*1000 for i in range(1, n_slices)] |
| 26 | − slice_source_at_stamps(self.source_file, regions_start, output_dir = self.output_dir) | |
| 26 | + slice_source_at_stamps(self.source_file, regions_start, | |
| 27 | + output_dir = self.output_dir, create_first=True) | |
| 27 | 28 | |
| 28 | 29 | def test_slice_start_beyond_end(self): |
| 29 | 30 | regions_start = [i*1000 for i in range(1, n_slices)] |
| 30 | 31 | regions_start += [count_samples_in_file(self.source_file) + 1000] |
| 31 | − slice_source_at_stamps(self.source_file, regions_start, output_dir = self.output_dir) | |
| 32 | + slice_source_at_stamps(self.source_file, regions_start, | |
| 33 | + output_dir = self.output_dir, create_first=True) | |
| 32 | 34 | |
| 33 | 35 | def test_slice_start_every_blocksize(self): |
| 34 | 36 | hopsize = 200 |
| 35 | − regions_start = [i*hopsize for i in range(1, n_slices)] | |
| 37 | + regions_start = [i*hopsize for i in range(0, n_slices)] | |
| 36 | 38 | slice_source_at_stamps(self.source_file, regions_start, output_dir = self.output_dir, |
| 37 | 39 | hopsize = 200) |
| 38 | 40 | |
| 41 | + def test_slice_start_every_half_blocksize(self): | |
| 42 | + hopsize = 200 | |
| 43 | + regions_start = [i*hopsize//2 for i in range(0, n_slices)] | |
| 44 | + slice_source_at_stamps(self.source_file, regions_start, | |
| 45 | + output_dir = self.output_dir, hopsize = 200) | |
| 46 | + | |
| 39 | 47 | def tearDown(self): |
| 40 | 48 | original_samples = count_samples_in_file(self.source_file) |
| 41 | 49 | written_samples = count_samples_in_directory(self.output_dir) |
| @@ -91,6 +99,19 @@ class aubio_slicing_with_ends_test_case(TestCase): · 49 unmodified lines | ||
| 91 | 99 | assert_equal(written_samples, expected_samples, |
| 92 | 100 | "number of samples written different from number of original samples") |
| 93 | 101 | |
| 102 | + def test_slice_start_and_ends_with_missing_end(self): | |
| 103 | + regions_start = [i*1000 for i in range(n_slices)] | |
| 104 | + regions_ends = [r-1 for r in regions_start[1:]] | |
| 105 | + slice_source_at_stamps(self.source_file, regions_start, regions_ends, | |
| 106 | + output_dir = self.output_dir) | |
| 107 | + written_samples = count_samples_in_directory(self.output_dir) | |
| 108 | + original_samples = count_samples_in_file(self.source_file) | |
| 109 | + total_files = count_files_in_directory(self.output_dir) | |
| 110 | + assert_equal(n_slices, total_files, | |
| 111 | + "number of slices created different from expected") | |
| 112 | + assert_equal(written_samples, original_samples, | |
| 113 | + "number of samples written different from number of original samples") | |
| 114 | + | |
| 94 | 115 | def tearDown(self): |
| 95 | 116 | shutil.rmtree(self.output_dir) |
| 96 | 117 | |
| @@ -133,7 +154,7 @@ class aubio_slicing_wrong_ends_test_case(TestCase): · 36 unmodified lines | ||
| 133 | 154 | regions_start = [i*1000 for i in range(1, n_slices)] |
| 134 | 155 | regions_end = None |
| 135 | 156 | slice_source_at_stamps (self.source_file, regions_start, regions_end, |
| 136 | − output_dir = self.output_dir) | |
| 157 | + output_dir = self.output_dir, create_first=True) | |
| 137 | 158 | total_files = count_files_in_directory(self.output_dir) |
| 138 | 159 | assert_equal(n_slices, total_files, |
| 139 | 160 | "number of slices created different from expected") |
| @@ -2,9 +2,9 @@ · 1 unmodified line | ||
| 2 | 2 | |
| 3 | 3 | from nose2 import main |
| 4 | 4 | from nose2.tools import params |
| 5 | −from numpy.testing import TestCase | |
| 5 | +from numpy.testing import TestCase, assert_equal | |
| 6 | 6 | from aubio import source |
| 7 | −from utils import list_all_sounds | |
| 7 | +from .utils import list_all_sounds | |
| 8 | 8 | |
| 9 | 9 | import warnings |
| 10 | 10 | warnings.filterwarnings('ignore', category=UserWarning, append=True) |
| @@ -51,9 +51,11 @@ class aubio_source_read_test_case(aubio_source_test_case_base): · 40 unmodified lines | ||
| 51 | 51 | def read_from_source(self, f): |
| 52 | 52 | total_frames = 0 |
| 53 | 53 | while True: |
| 54 | − _ , read = f() | |
| 54 | + samples , read = f() | |
| 55 | 55 | total_frames += read |
| 56 | − if read < f.hop_size: break | |
| 56 | + if read < f.hop_size: | |
| 57 | + assert_equal(samples[read:], 0) | |
| 58 | + break | |
| 57 | 59 | #result_str = "read {:.2f}s ({:d} frames in {:d} blocks at {:d}Hz) from {:s}" |
| 58 | 60 | #result_params = total_frames / float(f.samplerate), total_frames, total_frames//f.hop_size, f.samplerate, f.uri |
| 59 | 61 | #print (result_str.format(*result_params)) |
| @@ -66,7 +68,14 @@ class aubio_source_read_test_case(aubio_source_test_case_base): · 6 unmodified lines | ||
| 66 | 68 | except RuntimeError as e: |
| 67 | 69 | self.skipTest('failed opening with hop_s = {:d}, samplerate = {:d} ({:s})'.format(hop_size, samplerate, str(e))) |
| 68 | 70 | assert f.samplerate != 0 |
| 69 | − self.read_from_source(f) | |
| 71 | + read_frames = self.read_from_source(f) | |
| 72 | + if 'f_' in soundfile and samplerate == 0: | |
| 73 | + import re | |
| 74 | + f = re.compile('.*_\([0:9]*f\)_.*') | |
| 75 | + match_f = re.findall('([0-9]*)f_', soundfile) | |
| 76 | + if len(match_f) == 1: | |
| 77 | + expected_frames = int(match_f[0]) | |
| 78 | + self.assertEqual(expected_frames, read_frames) | |
| 70 | 79 | |
| 71 | 80 | @params(*list_of_sounds) |
| 72 | 81 | def test_samplerate_none(self, p): |
| @@ -149,13 +158,31 @@ class aubio_source_readmulti_test_case(aubio_source_read_test_case): · 76 unmodified lines | ||
| 149 | 158 | def read_from_source(self, f): |
| 150 | 159 | total_frames = 0 |
| 151 | 160 | while True: |
| 152 | − _, read = f.do_multi() | |
| 161 | + samples, read = f.do_multi() | |
| 153 | 162 | total_frames += read |
| 154 | − if read < f.hop_size: break | |
| 163 | + if read < f.hop_size: | |
| 164 | + assert_equal(samples[:,read:], 0) | |
| 165 | + break | |
| 155 | 166 | #result_str = "read {:.2f}s ({:d} frames in {:d} channels and {:d} blocks at {:d}Hz) from {:s}" |
| 156 | 167 | #result_params = total_frames / float(f.samplerate), total_frames, f.channels, int(total_frames/f.hop_size), f.samplerate, f.uri |
| 157 | 168 | #print (result_str.format(*result_params)) |
| 158 | 169 | return total_frames |
| 159 | 170 | |
| 171 | +class aubio_source_with(aubio_source_test_case_base): | |
| 172 | + | |
| 173 | + #@params(*list_of_sounds) | |
| 174 | + @params(*list_of_sounds) | |
| 175 | + def test_read_from_mono(self, filename): | |
| 176 | + total_frames = 0 | |
| 177 | + hop_size = 2048 | |
| 178 | + with source(filename, 0, hop_size) as input_source: | |
| 179 | + assert_equal(input_source.hop_size, hop_size) | |
| 180 | + #assert_equal(input_source.samplerate, samplerate) | |
| 181 | + total_frames = 0 | |
| 182 | + for frames in input_source: | |
| 183 | + total_frames += frames.shape[-1] | |
| 184 | + # check we read as many samples as we expected | |
| 185 | + assert_equal(total_frames, input_source.duration) | |
| 186 | + | |
| 160 | 187 | if __name__ == '__main__': |
| 161 | 188 | main() |
| @@ -0,0 +1,93 @@ | ||
| 1 | +#! /usr/bin/env python | |
| 2 | + | |
| 3 | +"""A brute force test using `sink` to create and write samples to a stereo | |
| 4 | +file, then `source` to check the correct content is read from the files.""" | |
| 5 | + | |
| 6 | +import os.path | |
| 7 | +import unittest | |
| 8 | +import aubio | |
| 9 | +import numpy as np | |
| 10 | +from numpy.testing import assert_equal | |
| 11 | +from .utils import get_tmp_sink_path | |
| 12 | + | |
| 13 | +class aubio_source_test_case(unittest.TestCase): | |
| 14 | + | |
| 15 | + def test_read_from_mono(self): | |
| 16 | + out = get_tmp_sink_path() | |
| 17 | + samplerate = 44100 | |
| 18 | + hop_size = 256 | |
| 19 | + blocks = 10 | |
| 20 | + channels = 1 | |
| 21 | + write_samples = np.ones([channels, hop_size], dtype=aubio.float_type) | |
| 22 | + write_samples *= .5 | |
| 23 | + self.check_write_and_read(samplerate, channels, hop_size, blocks, | |
| 24 | + write_samples) | |
| 25 | + | |
| 26 | + def test_read_from_stereo(self): | |
| 27 | + out = get_tmp_sink_path() | |
| 28 | + samplerate = 44100 | |
| 29 | + hop_size = 256 | |
| 30 | + blocks = 10 | |
| 31 | + channels = 1 | |
| 32 | + write_samples = np.ones([channels, hop_size], dtype=aubio.float_type) | |
| 33 | + write_samples *= .5 | |
| 34 | + self.check_write_and_read(samplerate, channels, hop_size, blocks, | |
| 35 | + write_samples) | |
| 36 | + | |
| 37 | + def test_read_from_half_stereo(self): | |
| 38 | + samplerate = 16000 | |
| 39 | + channels = 2 | |
| 40 | + hop_size = 512 | |
| 41 | + blocks = 10 | |
| 42 | + write_samples = np.ones([channels, hop_size], dtype=aubio.float_type) | |
| 43 | + write_samples *= .5 | |
| 44 | + write_samples[1, :] = 0 | |
| 45 | + self.check_write_and_read(samplerate, channels, hop_size, blocks, | |
| 46 | + write_samples) | |
| 47 | + | |
| 48 | + def test_read_from_cancelling_channels(self): | |
| 49 | + samplerate = 16000 | |
| 50 | + channels = 2 | |
| 51 | + hop_size = 512 | |
| 52 | + blocks = 10 | |
| 53 | + write_samples = np.ones([channels, hop_size], dtype=aubio.float_type) | |
| 54 | + write_samples *= .5 | |
| 55 | + write_samples[1] *= -1 | |
| 56 | + self.check_write_and_read(samplerate, channels, hop_size, blocks, | |
| 57 | + write_samples) | |
| 58 | + | |
| 59 | + def test_read_from_strange_three_channels(self): | |
| 60 | + samplerate = 8000 | |
| 61 | + channels = 3 | |
| 62 | + hop_size = 123 | |
| 63 | + blocks = 10 | |
| 64 | + write_samples = np.ones([channels, hop_size], dtype=aubio.float_type) | |
| 65 | + write_samples *= .5 | |
| 66 | + write_samples[1, :] = 0 | |
| 67 | + self.check_write_and_read(samplerate, channels, hop_size, blocks, | |
| 68 | + write_samples) | |
| 69 | + | |
| 70 | + def check_write_and_read(self, samplerate, channels, | |
| 71 | + hop_size, blocks, write_samples): | |
| 72 | + expected_mono = np.sum(write_samples, axis=0)/write_samples.shape[0] | |
| 73 | + out = get_tmp_sink_path() | |
| 74 | + snk = aubio.sink(out, samplerate, channels=channels) | |
| 75 | + for i in range(blocks): | |
| 76 | + snk.do_multi(write_samples, hop_size) | |
| 77 | + # close the sink before reading from it | |
| 78 | + snk.close() | |
| 79 | + | |
| 80 | + src = aubio.source(out, samplerate, hop_size) | |
| 81 | + for i in range(blocks): | |
| 82 | + read_samples, read = src.do_multi() | |
| 83 | + assert_equal (read_samples, write_samples) | |
| 84 | + assert_equal (read, hop_size) | |
| 85 | + | |
| 86 | + src.seek(0) | |
| 87 | + for i in range(blocks): | |
| 88 | + read_samples, read = src() | |
| 89 | + assert_equal (read, hop_size) | |
| 90 | + assert_equal (read_samples, expected_mono) | |
| 91 | + | |
| 92 | +if __name__ == '__main__': | |
| 93 | + unittest.main() | |
| @@ -0,0 +1,91 @@ | ||
| 1 | +#! /usr/bin/env python | |
| 2 | + | |
| 3 | +from unittest import main | |
| 4 | +from numpy.testing import TestCase, assert_equal, assert_almost_equal | |
| 5 | +import aubio | |
| 6 | + | |
| 7 | +class aubio_tempo_default(TestCase): | |
| 8 | + | |
| 9 | + def test_members(self): | |
| 10 | + o = aubio.tempo() | |
| 11 | + assert_equal ([o.buf_size, o.hop_size, o.method, o.samplerate], | |
| 12 | + [1024,512,'default',44100]) | |
| 13 | + | |
| 14 | +class aubio_tempo_params(TestCase): | |
| 15 | + | |
| 16 | + samplerate = 44100 | |
| 17 | + | |
| 18 | + def setUp(self): | |
| 19 | + self.o = aubio.tempo(samplerate = self.samplerate) | |
| 20 | + | |
| 21 | + def test_get_delay(self): | |
| 22 | + self.assertEqual(self.o.get_delay(), 0) | |
| 23 | + | |
| 24 | + def test_set_delay(self): | |
| 25 | + val = 256 | |
| 26 | + self.o.set_delay(val) | |
| 27 | + assert_equal (self.o.get_delay(), val) | |
| 28 | + | |
| 29 | + def test_get_delay_s(self): | |
| 30 | + self.assertEqual(self.o.get_delay_s(), 0.) | |
| 31 | + | |
| 32 | + def test_set_delay_s(self): | |
| 33 | + val = .05 | |
| 34 | + self.o.set_delay_s(val) | |
| 35 | + assert_almost_equal (self.o.get_delay_s(), val) | |
| 36 | + | |
| 37 | + def test_get_delay_ms(self): | |
| 38 | + self.assertEqual(self.o.get_delay_ms(), 0.) | |
| 39 | + | |
| 40 | + def test_set_delay_ms(self): | |
| 41 | + val = 50. | |
| 42 | + self.o.set_delay_ms(val) | |
| 43 | + assert_almost_equal (self.o.get_delay_ms(), val) | |
| 44 | + | |
| 45 | + def test_get_threshold(self): | |
| 46 | + assert_almost_equal(self.o.get_threshold(), 0.3) | |
| 47 | + | |
| 48 | + def test_set_threshold(self): | |
| 49 | + val = .1 | |
| 50 | + self.o.set_threshold(val) | |
| 51 | + assert_almost_equal (self.o.get_threshold(), val) | |
| 52 | + | |
| 53 | + def test_get_silence(self): | |
| 54 | + self.assertEqual(self.o.get_silence(), -90.) | |
| 55 | + | |
| 56 | + def test_set_silence(self): | |
| 57 | + val = -50. | |
| 58 | + self.o.set_silence(val) | |
| 59 | + assert_almost_equal (self.o.get_silence(), val) | |
| 60 | + | |
| 61 | + def test_get_last(self): | |
| 62 | + self.assertEqual(self.o.get_last(), 0.) | |
| 63 | + | |
| 64 | + def test_get_last_s(self): | |
| 65 | + self.assertEqual(self.o.get_last_s(), 0.) | |
| 66 | + | |
| 67 | + def test_get_last_ms(self): | |
| 68 | + self.assertEqual(self.o.get_last_ms(), 0.) | |
| 69 | + | |
| 70 | + def test_get_period(self): | |
| 71 | + self.assertEqual(self.o.get_period(), 0.) | |
| 72 | + | |
| 73 | + def test_get_period_s(self): | |
| 74 | + self.assertEqual(self.o.get_period_s(), 0.) | |
| 75 | + | |
| 76 | + def test_get_last_tatum(self): | |
| 77 | + self.assertEqual(self.o.get_last_tatum(), 0.) | |
| 78 | + | |
| 79 | + def test_set_tatum_signature(self): | |
| 80 | + self.o.set_tatum_signature(8) | |
| 81 | + self.o.set_tatum_signature(64) | |
| 82 | + self.o.set_tatum_signature(1) | |
| 83 | + | |
| 84 | + def test_set_wrong_tatum_signature(self): | |
| 85 | + with self.assertRaises(ValueError): | |
| 86 | + self.o.set_tatum_signature(101) | |
| 87 | + with self.assertRaises(ValueError): | |
| 88 | + self.o.set_tatum_signature(0) | |
| 89 | + | |
| 90 | +if __name__ == '__main__': | |
| 91 | + main() | |