music.profile

music.profile(adict, sample_rate=44100)[source]

Summarize a namespace of variables.

Sorts the names by what they hold, measures every array in it, and reads each array as PCM samples or as parametrisation. Written for looking at a piece of synthesis code mid-flight: hand it locals() and it says which of the names are sounds, how long each is, and which of the numbers look like frequencies, decibels or pitches.

Parameters:
adictdict

The namespace to describe, mapping names to values – typically locals() or vars() from a piece of synthesis code.

sample_ratescalar

The sample rate the durations are reckoned at.

Returns:
dict

d['type'] sorts the names: 'scalar' for numbers and strings, 'collections' for dicts, lists, sets and arrays, and 'other' for everything else. d['analyses']['ndarray'] maps each array name to its measurements – shape, samples, seconds at sample_rate, mean, mean_square, rms, minimum, maximum, and the mean and standard deviation of the RMS taken block by block, whose spread is what discontinuity shows up in. d['guesses'] maps the same names to readings of those measurements, each a (reading, reason) pair.

See also

amp_to_db

the conversion the decibel reading above is about.

hz_to_midi

the conversion the pitch reading is about.

Notes

The measurements are measurements; the guesses are guesses, which is why they are kept apart. A guess carries the reason that produced it so that a caller can disagree with it:

  • A large array whose mean sits at zero and whose values are bounded by 1 or by a power of two is read as PCM samples.

  • A short array with an offset mean is read as parametrisation – values meant to be used rather than heard, often driving a rhythm.

  • Values reaching into the hundreds are read as frequencies in Hz.

  • Values within [0, 150] are read as MIDI pitches or semitone intervals when they are integers or move in steps under 10, and as decibels when they move in steps of tens.

This function used to raise NotImplementedError: its body had been a commented-out sketch since it was written, and the docstring above was a specification rather than a description. It is now the second of these.

Examples

>>> sound = np.sin(2 * np.pi * 440 * np.arange(88200) / 44100)
>>> summary = profile({'s': sound, 'freqs': np.array([220., 440.])})
>>> sorted(summary['type']['collections'])
['freqs', 's']
>>> summary['analyses']['ndarray']['s']['seconds']
2.0
>>> [reading for reading, _reason in summary['guesses']['s']]
['pcm samples']