{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Pitch manipulation and Praat commands" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Another common use of Praat functionality is to manipulate certain features of an existing audio fragment. For example, in the context of a perception experiment one might want to change the pitch contour of an existing audio stimulus while keeping the rest of the acoustic features the same. Parselmouth can then be used to access the Praat algorithms that accompish this, from Python." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Since this Praat `Manipulation` functionality has currently not been ported to Parselmouth's Python interface, we will need to use Parselmouth interface to access *raw* Praat commands." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In this example, we will increase the pitch contour of an audio recording of the word *\"four\"*, [4_b.wav](audio/4_b.wav), by one octave. To do so, let's start by importing Parselmouth and opening the audio file:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import parselmouth\n", "\n", "sound = parselmouth.Sound(\"audio/4_b.wav\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can also listen to this audio fragment:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from IPython.display import Audio\n", "Audio(data=sound.values, rate=sound.sampling_frequency)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "However, now we want to use the Praat `Manipulation` functionality, but unfortunately, Parselmouth does not yet contain a `Manipulation` class and the necessary functionality is not directly accessible through the [Sound](../api_reference.rst#parselmouth.Sound) object `sound`. To directly access the Praat commands conveniently from Python, we can make use of the [parselmouth.praat.call](../api_reference.rst#parselmouth.praat.call) function." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from parselmouth.praat import call\n", "\n", "manipulation = call(sound, \"To Manipulation\", 0.01, 75, 600)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "type(manipulation)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Note how we first pass in the object(s) that would be selected in Praat's object list. The next argument to this function is the name of the command as it would be used in a script or can be seen in the Praat user interface. Finally, the arguments to this command's parameters are passed to the function (in this case, Praat's default values for *\"Time step (s)\"*, *\"Minimum pitch (Hz)\"*, and *\"Maximum pitch (Hz)\"*). This call to `parselmouth.praat.call` will then return the result of the command as a Python type or Parselmouth object. In this case, a Praat `Manipulation` object would be created, so our function returns a `parselmouth.Data` object, as a `parselmouth.Manipulation` class does not exist in Parselmouth. However, we can still query the class name the underlying Praat object has:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "manipulation.class_name" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Next, we can continue using Praat functionality to further use this `manipulation` object similar to how one would achieve this in Praat. Here, note how we can mix normal Python (e.g. integers and lists), together with the normal use of Parselmouth as Python library (e.g., `sound.xmin`) as well as with the `parselmouth.praat.call` function." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "pitch_tier = call(manipulation, \"Extract pitch tier\")\n", "\n", "call(pitch_tier, \"Multiply frequencies\", sound.xmin, sound.xmax, 2)\n", "\n", "call([pitch_tier, manipulation], \"Replace pitch tier\")\n", "sound_octave_up = call(manipulation, \"Get resynthesis (overlap-add)\")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "type(sound_octave_up)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The last invocation of `call` resulted in a Praat `Sound` object being created and returned. Because Parselmouth knows that this type corresponds to a `parselmouth.Sound` Python object, the Python type of this object is not a `parselmouth.Data`. Rather, this object is now equivalent to the one we created at the start of this example. As such, we can use this new object normally, calling methods and accessing its contents. Let's listen and see if we succeeded in increasing the pitch by one octave:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "Audio(data=sound_octave_up.values, rate=sound_octave_up.sampling_frequency)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "And similarly, we could also for example save the sound to a new file." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "sound_octave_up.save(\"4_b_octave_up.wav\", \"WAV\")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "Audio(filename=\"4_b_octave_up.wav\")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Clean up the created audio file again\n", "!rm 4_b_octave_up.wav" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can of course also turn this combination of commands into a custom function, to be reused in later code:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def change_pitch(sound, factor):\n", " manipulation = call(sound, \"To Manipulation\", 0.01, 75, 600)\n", "\n", " pitch_tier = call(manipulation, \"Extract pitch tier\")\n", "\n", " call(pitch_tier, \"Multiply frequencies\", sound.xmin, sound.xmax, factor)\n", "\n", " call([pitch_tier, manipulation], \"Replace pitch tier\")\n", " return call(manipulation, \"Get resynthesis (overlap-add)\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Using Jupyter widgets, one can then change the audio file or the pitch change factor, and interactively hear how this sounds." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "*To try this for yourself, open an online, interactive version of this notebook on Binder! (see link at the top of this notebook)*" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import ipywidgets\n", "import glob\n", "\n", "def interactive_change_pitch(audio_file, factor):\n", " sound = parselmouth.Sound(audio_file)\n", " sound_changed_pitch = change_pitch(sound, factor)\n", " return Audio(data=sound_changed_pitch.values, rate=sound_changed_pitch.sampling_frequency)\n", "\n", "w = ipywidgets.interact(interactive_change_pitch,\n", " audio_file=ipywidgets.Dropdown(options=sorted(glob.glob(\"audio/*.wav\")), value=\"audio/4_b.wav\"),\n", " factor=ipywidgets.FloatSlider(min=0.25, max=4, step=0.05, value=1.5))" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.8.7" }, "nbsphinx": { "timeout": 60 } }, "nbformat": 4, "nbformat_minor": 4 }