Skip to content

API Reference

Basic

Contains basic functions for generating and manipulating sound waves.

composition(f, g)

Returns the composition of the given functions, that is, f(g(x)).

Source code in anymusic/basic.py
def composition(f: Callable[[S], T], g: Callable[[R], S]) -> Callable[[R], T]:
    """
    Returns the composition of the given functions, that is, f(g(x)).
    """
    return lambda t: f(g(t))

envelope(f, multiplier)

Returns a function that is the given function multiplied by the given envelope.

Source code in anymusic/basic.py
def envelope(f: Audio, multiplier: Envelope) -> Audio:
    """
    Returns a function that is the given function multiplied by the given envelope.
    """
    return lambda t: f(t) * multiplier(t)

sawtooth(freq)

Returns a sawtooth wave of the given frequency.

Source code in anymusic/basic.py
def sawtooth(freq: Frequency) -> Audio:
    """
    Returns a sawtooth wave of the given frequency.
    """
    return lambda t: (t * freq * 2) % 2 - 1

scale(f, factor)

Returns a scaled version of the given function.

Source code in anymusic/basic.py
def scale(f: Audio, factor: float) -> Audio:
    """
    Returns a scaled version of the given function.
    """
    return lambda t: f(t) * factor

shift(f, offset)

Returns a function that is shifted by the given offset.

Source code in anymusic/basic.py
def shift(f: Audio, offset: Time) -> Audio:
    """
    Returns a function that is shifted by the given offset.
    """
    return lambda t: f(t - offset)

sine(freq)

Returns a sine wave of the given frequency.

Source code in anymusic/basic.py
def sine(freq: Frequency) -> Audio:
    """
    Returns a sine wave of the given frequency.
    """
    return lambda t: math.sin(t * freq * math.pi * 2)

square(freq)

Returns a square wave of the given frequency.

Source code in anymusic/basic.py
def square(freq: Frequency) -> Audio:
    """
    Returns a square wave of the given frequency.
    """
    return lambda t: 1 if math.sin(t * freq * math.pi * 2) > 0 else -1

stack(fs)

Returns sum of the given functions.

Source code in anymusic/basic.py
def stack(fs: Iterable[Audio]) -> Audio:
    """
    Returns sum of the given functions.
    """
    return lambda t: functools.reduce(lambda x, y: x + y, [f(t) for f in fs])

strip(f, start, end)

Returns a function that is 0 before start and after end.

Source code in anymusic/basic.py
def strip(f: Audio, start: Time, end: Time) -> Audio:
    """
    Returns a function that is 0 before start and after end.
    """
    return lambda t: f(t) if start <= t <= end else 0

triangle(freq)

Returns a triangle wave of the given frequency.

Source code in anymusic/basic.py
def triangle(freq: Frequency) -> Audio:
    """
    Returns a triangle wave of the given frequency.
    """
    return lambda t: 1 - abs((t * freq * 2) % 2 - 1) * 2

Timbre

Different timbres.

default_piano()

Returns a timbre that sounds like a electric piano.

Source code in anymusic/timbre.py
def default_piano() -> Timbre:
    """
    Returns a timbre that sounds like a electric piano.
    """
    multipliers = [1 / i for i in range(1, 8)]
    return piano(multipliers)

piano(overtunes_multiplier)

Returns a timbre that sounds like a piano. It has overtones proportional to base frequency for n in overtunes_multiplier.

Note: overtunes_multiplier[0] should be 1 in most cases in order to preserve base frequency loudness.

Source code in anymusic/timbre.py
def piano(overtunes_multiplier: Iterable[float]) -> Timbre:
    """
    Returns a timbre that sounds like a piano.
    It has overtones proportional to base frequency for n in overtunes_multiplier.

    Note: overtunes_multiplier[0] should be 1 in most cases in order to preserve base frequency loudness.
    """

    def _piano(freq: Frequency) -> Audio:
        overtune_waves = [scale(sine(freq * i), multiplier) for i, multiplier in enumerate(overtunes_multiplier)]
        return stack(overtune_waves)

    return _piano

sawtooth_timbre()

Returns a timbre that uses sawtooth waves.

Source code in anymusic/timbre.py
def sawtooth_timbre() -> Timbre:
    """
    Returns a timbre that uses sawtooth waves.
    """
    return sawtooth

sine_timbre()

Returns a timbre that uses sine waves.

Source code in anymusic/timbre.py
def sine_timbre() -> Timbre:
    """
    Returns a timbre that uses sine waves.
    """
    return sine

square_timbre()

Returns a timbre that uses square waves.

Source code in anymusic/timbre.py
def square_timbre() -> Timbre:
    """
    Returns a timbre that uses square waves.
    """
    return square

triangle_timbre()

Returns a timbre that uses triangle waves.

Source code in anymusic/timbre.py
def triangle_timbre() -> Timbre:
    """
    Returns a timbre that uses triangle waves.
    """
    return triangle

Tuning

Make human-recognizable regular sounds.

equal_step_tuning(base_freq, base, eqtm)

Traditional music tuning systems use C * 2^( a + b * 1/12 ) as the frequency where it is the a * 12 + b semitones above the base frequency. However it can be further generalized to C * BASE^( a + b * EQTM ) where EQTM is the equal temperament.

For example, for a 24-EDO scale, BASE is 2 and EQTM is 1/24.

Source code in anymusic/tuning.py
def equal_step_tuning(base_freq: Frequency, base: float, eqtm: float) -> Tuning:
    """
    Traditional music tuning systems use C * 2^( a + b * 1/12 ) as the frequency where it is the a * 12 + b semitones
    above the base frequency.
    However it can be further generalized to C * BASE^( a + b * EQTM ) where EQTM is the equal temperament.

    For example, for a 24-EDO scale, BASE is 2 and EQTM is 1/24.
    """
    return lambda a, b: base_freq * (base ** (a + b * eqtm))

twelve_edo()

Returns the traditional 12-EDO tuning.

Source code in anymusic/tuning.py
def twelve_edo() -> Tuning:
    """
    Returns the traditional 12-EDO tuning.
    """
    # Starting from C0, the base frequency is 16.3 Hz.
    return equal_step_tuning(16.3, 2, 1 / 12)

Utils

Miscellaneous functions.

fade_out_exp(lasting, exp)

Returns an exponential fade out multiplier.

Source code in anymusic/util.py
def fade_out_exp(lasting: Time, exp: float) -> Envelope:
    """
    Returns an exponential fade out multiplier.
    """
    return lambda t: (lasting - t) ** exp / lasting ** exp

fade_out_linear(lasting)

Returns a linear fade out multiplier.

Source code in anymusic/util.py
def fade_out_linear(lasting: Time) -> Envelope:
    """
    Returns a linear fade out multiplier.
    """
    return lambda t: 1 - t / lasting

note_to_audio(note, tuning, timbre, effect=None)

Returns the audio of the given note. It will be shifted and stripped to the given start and end time.

Source code in anymusic/util.py
def note_to_audio(
    note: Note, tuning: Tuning, timbre: Timbre, effect: Optional[Callable[[Audio], Audio]] = None
) -> Audio:
    """
    Returns the audio of the given note. It will be shifted and stripped to the given start and end time.
    """
    oct, sem, start, end = note  # pylint: disable=W0622
    audio = timbre(tuning(oct, sem))
    if effect is not None:
        audio = effect(audio)
    return strip(shift(audio, start), start, end)

Wave

Wave file manipulation.

read_file(filename)

Reads a wave file. Note that it may take a lot of memory if the file is large.

Source code in anymusic/wave.py
def read_file(filename: str) -> Audio:
    """
    Reads a wave file. Note that it may take a lot of memory if the file is large.
    """
    with wave.open(filename, "r") as file:
        nchannels = file.getnchannels()
        sampwidth = file.getsampwidth()
        framerate = file.getframerate()
        nframes = file.getnframes()
        frames = file.readframes(nframes)
        return lambda t: int.from_bytes(
            frames[int(t * framerate) * nchannels * sampwidth : (int(t * framerate) + 1) * nchannels * sampwidth],
            "little",
            signed=True,
        ) / (2 ** (sampwidth * 8 - 1) - 1)

write_and_normalize_file(filename, f, duration, framerate=44100, sampwidth=2, progress_bar_interval=50000)

A variant of write_file that normalizes the audio into [-1, 1] before writing. Note that it takes double the time as it tries to sample the entire Audio function into memory and find the maximum amplitude.

Source code in anymusic/wave.py
def write_and_normalize_file(
    filename: str,
    f: Audio,
    duration: float,
    framerate: int = 44100,  # 44.1 kHz
    sampwidth: int = 2,  # 16 bits
    progress_bar_interval: int | None = 50000,  # In frames
) -> None:
    """
    A variant of write_file that normalizes the audio into [-1, 1] before writing.
    Note that it takes double the time as it tries to sample the entire Audio function into memory and find the maximum
    amplitude.
    """
    total_frames = int(duration * framerate)
    maximum = max(abs(f(t / framerate)) for t in range(total_frames))
    normalized = scale(f, 1 / maximum)
    write_file(filename, normalized, duration, framerate, sampwidth, progress_bar_interval)

write_file(filename, f, duration, framerate=44100, sampwidth=2, progress_bar_interval=50000)

Writes a wave file. If progress_bar_interval is not None, then a progress bar will be printed to the console every progress_bar_interval frames.

Source code in anymusic/wave.py
def write_file(
    filename: str,
    f: Audio,
    duration: float,
    framerate: int = 44100,  # 44.1 kHz
    sampwidth: int = 2,  # 16 bits
    progress_bar_interval: int | None = 50000,  # In frames
) -> None:
    """
    Writes a wave file. If `progress_bar_interval` is not `None`, then a progress bar will be printed to the console
    every `progress_bar_interval` frames.
    """
    # pylint: disable=E1101
    total_frames = int(duration * framerate)
    with wave.open(filename, "w") as file:
        file.setnchannels(1)
        file.setsampwidth(sampwidth)
        file.setframerate(framerate)
        file.setnframes(total_frames)
        for t in range(total_frames):
            amp = int(f(t / framerate) * (2 ** (sampwidth * 8 - 1) - 1))
            file.writeframesraw(amp.to_bytes(sampwidth, "little", signed=True))
            if progress_bar_interval is not None and t % progress_bar_interval == 0:
                print(f"Writing {t}/{total_frames} frames", end="\r")
        if progress_bar_interval is not None:
            print(f"Writing {total_frames}/{total_frames} frames")
            print(f"Exported {filename}")