PK2(OKM1-beatmachine/__init__.py""" The Beat Machine is a library for playing with beats of songs. """ __version__ = "2.1.0" from . import loader, editor, effects PKU O {\\beatmachine/__main__.pyimport json from click import command, option import beatmachine as bm @command() @option("--input", help="File to process.", required=True) @option("--effects", help="JSON representation of effects to apply.", required=True) @option("--output", help="Output mp3 file path.", required=True) def main(input, effects, output): beats = bm.loader.load_beats_by_signal(input) effects = [bm.effects.load_from_dict(e) for e in json.loads(effects)] result = bm.editor.apply_effects(beats, effects) return sum(result).export(output) if __name__ == "__main__": main() PKU Oȃbeatmachine/editor.pyfrom typing import Iterable, Generator from pydub import AudioSegment from beatmachine.effects.base import BaseEffect def apply_effects( beats: Iterable[AudioSegment], effects: Iterable[BaseEffect] ) -> Generator[AudioSegment, None, None]: """ Applies a collection of effects to a song represented by a collection of beats. :param beats: Beats to apply the given effects to. :param effects: Effects to apply to the song. :return: A list of beats with the given effects applied. This can be coalesced into a single audio clip with `sum`. """ for effect in effects: beats = effect(beats) yield from beats PK U Obeatmachine/loader.pyfrom typing import BinaryIO, Union, Generator import numpy import pydub from madmom.audio import Signal from madmom.features import DBNBeatTrackingProcessor, RNNBeatProcessor from pydub import AudioSegment def load_beats_by_signal( fp: Union[str, BinaryIO], audio_format: str = "mp3", min_bpm: int = 60, max_bpm: int = 300, fps: int = 100, ) -> Generator[AudioSegment, None, None]: """ A generator that loads beats based on audio data itself, handling variations in tempo. :param fp: Path to or file-like object of the audio to load. :param audio_format: Audio data format. :param min_bpm: Minimum permissible BPM. :param max_bpm: Maximum permissible BPM. :param fps: Resolution to process beats at. :return: A generator yielding each beat of the input song as a PyDub AudioSegment. Note that the initial beat-finding process is comparatively time-consuming and can take up to a few minutes for longer songs. """ tracker = DBNBeatTrackingProcessor(min_bpm=min_bpm, max_bpm=max_bpm, fps=fps) processor = RNNBeatProcessor() times = tracker(processor(Signal(fp))) audio = pydub.AudioSegment.from_file(fp, format=audio_format) last_time_s = 0 for i, time_s in numpy.ndenumerate(times): yield audio[int(last_time_s * 1000) : int(time_s * 1000)] last_time_s = time_s def load_beats_by_bpm( fp: Union[str, BinaryIO], bpm: int, audio_format: str = "mp3" ) -> Generator[AudioSegment, None, None]: """ A generator that loads beats strictly by a given BPM assuming no fluctuations in tempo. Significantly faster than `load_beats_by_signal` however can be less accurate, especially in live performances. :param fp: Path to or file-like object of the audio to load. :param bpm: Song BPM. This can sometimes be found online. :param audio_format: Audio data format. :return: A generator yielding each beat of the input song as a PyDub AudioSegment. """ audio = pydub.AudioSegment.from_file(fp, format=audio_format) beat_size_ms = 60_000 // bpm for beat_start_ms in range(0, len(audio), beat_size_ms): yield audio[beat_start_ms : beat_start_ms + beat_size_ms] PKKN@beatmachine/effects/__init__.py""" The `effects` module provides a base effect class as well as some sample implementations of song effects. """ from . import base, periodic, temporal load_from_dict = base.EffectRegistry.load_effect_from_dict PKT OgY Y beatmachine/effects/base.pyimport abc from typing import Generator, Iterable from pydub import AudioSegment class EffectRegistry(type): """ The EffectRegistry is a metaclass that serves to track all loadable effects. """ effects = {} def __new__(mcs, name, bases, class_dict): cls = type.__new__(mcs, name, bases, class_dict) try: if name not in mcs.effects: mcs.effects[cls.__effect_name__] = cls except AttributeError as e: raise AttributeError( "Attempted to register an effect class without an __effect_name__ attribute" ) from e return cls @staticmethod def load_effect_from_dict(effect: dict) -> "BaseEffect": """ Loads an effect based on a key-value definition. This is represented as a Python dictionary but could be loaded from anywhere, i.e. JSON data. A "type" key is required to determine which effect to load. All other values are passed directly as keyword arguments to the effect constructor. :param effect: Effect representation to load. :raises KeyError: if no effect with the given name was found. :raises ValueError: if the effect failed to load, likely due to an invalid/missing parameter. :return: An effect based on the given definition. """ if "type" not in effect: raise KeyError("Effect definition missing `type` key") effect_type = effect["type"] try: kwargs = effect.copy() del kwargs["type"] return EffectRegistry.effects[effect_type](**kwargs) except KeyError as e: raise KeyError(f"Unknown effect `{effect_type}`") from e except ValueError as e: raise ValueError(f"An effect of type `{effect_type}` failed to load") from e class EffectABCMeta(EffectRegistry, abc.ABCMeta): """ An EffectABCMeta serves as a metaclass for effects inheriting from BaseEffect to avoid metaclass conflict issues. """ pass class BaseEffect(abc.ABC): """ A BaseEffect provides abstract methods for all attributes necessary for a valid effect. """ __abstract__ = True @property @abc.abstractmethod def __effect_name__(self) -> str: """ __effect_name__ is the name of this effect. """ raise NotImplementedError @abc.abstractmethod def __call__( self, beats: Iterable[AudioSegment] ) -> Generator[AudioSegment, None, None]: """ Applies this effect to a given list of beats. :param beats: An iterable of beats to process. :return: A generator that yields modified beats, potentially in a different order or with a different length. """ raise NotImplementedError PK(Oj CCbeatmachine/effects/periodic.pyimport abc from typing import Callable, Optional import deprecation from pydub import AudioSegment from beatmachine.effects.base import BaseEffect, EffectABCMeta class PeriodicEffect(BaseEffect, abc.ABC): """ A PeriodicEffect is an effect that gets applied to beats at a fixed interval, i.e. every other beat. A PeriodicEffect with a period of 1 gets applied to every single beat. """ def __init__(self, *, period: int = 1): if period <= 0: raise ValueError(f"Effect period must be >= 0, but was {period}") self.period = period def process_beat(self, beat: AudioSegment) -> Optional[AudioSegment]: """ Processes a single beat. :param beat: Beat to process. :return: Updated beat or None if it should be removed. """ raise NotImplementedError def __call__(self, beats): for i, beat in enumerate(beats): result = self.process_beat(beat) if (i - 1) % self.period == 0 else beat if result is not None: yield result def __eq__(self, other): return isinstance(other, self.__class__) and self.period == other.period class SilenceEveryNth(PeriodicEffect, metaclass=EffectABCMeta): """ A periodic effect that silences beats, retaining their length. """ __effect_name__ = "silence" def __init__( self, silence_producer: Callable[[int], AudioSegment] = AudioSegment.silent, *, period: int = 1, ): super().__init__(period=period) self.silence_producer = silence_producer def process_beat(self, beat: AudioSegment) -> Optional[AudioSegment]: return self.silence_producer(len(beat)) def __eq__(self, other): return ( super(SilenceEveryNth, self).__eq__(other) and self.silence_producer == other.silence_producer ) class RemoveEveryNth(PeriodicEffect, metaclass=EffectABCMeta): """ A periodic effect that completely removes beats. """ __effect_name__ = "remove" def __init__(self, *, period: int = 1): if period < 2: raise ValueError(f"`remove` effect period must be >= 2, but was {period}") super().__init__(period=period) def process_beat(self, beat: AudioSegment) -> Optional[AudioSegment]: return None class CutEveryNth(PeriodicEffect, metaclass=EffectABCMeta): """ A periodic effect that cuts beats in half. """ __effect_name__ = "cut" def __init__(self, *, period: int = 1, denominator: int = 2, take_index: int = 0): super().__init__(period=period) self.denominator = denominator self.take_index = take_index def process_beat(self, beat_audio): size = len(beat_audio) // self.denominator offset = self.take_index * size return beat_audio[offset : offset + size] def __eq__(self, other): return ( isinstance(other, CutEveryNth) and other.period == self.period and other.denominator == self.denominator and other.take_index == self.take_index ) @deprecation.deprecated( deprecated_in="2.1.0", details="Succeeded by CutEveryNth, whose defaults have the same behavior as CutEveryNthInHalf.", ) class CutEveryNthInHalf(CutEveryNth): __effect_name__ = "_cut_old" class ReverseEveryNth(PeriodicEffect, metaclass=EffectABCMeta): """ A periodic effect that reverses beats. """ __effect_name__ = "reverse" def process_beat(self, beat_audio): return beat_audio.reverse() class RepeatEveryNth(PeriodicEffect, metaclass=EffectABCMeta): """ A periodic effect that repeats beats a specified number of times. """ __effect_name__ = "repeat" def __init__(self, *, period: int = 1, times: int = 2): if times < 2: raise ValueError( f"Repeat effect must have `times` >= 2, but instead got {times}" ) super().__init__(period=period) self.times = times def process_beat(self, beat_audio): return beat_audio * self.times def __eq__(self, other): return super(RepeatEveryNth, self).__eq__(other) and self.times == other.times PK(OȞaabeatmachine/effects/temporal.pyimport itertools import random from typing import Iterable, Generator, List, T from pydub import AudioSegment from beatmachine.effects.base import BaseEffect, EffectABCMeta class RandomizeAllBeats(BaseEffect, metaclass=EffectABCMeta): """ An effect that randomizes the order of every single beat of a song. """ __effect_name__ = "randomize" def __call__(self, beats): shuffled_beats = list(beats) random.shuffle(shuffled_beats) yield from shuffled_beats def __eq__(self, other): return isinstance(other, RandomizeAllBeats) def chunks(iterable: Iterable[T], size=10) -> Generator[List[T], None, None]: iterator = iter(iterable) for first in iterator: yield list(itertools.chain([first], itertools.islice(iterator, size - 1))) class SwapBeats(BaseEffect, metaclass=EffectABCMeta): """ An effect that swaps every two specified beats. For example, specifying periods 2 and 4 would result in every second and fourth beats being swapped. """ __effect_name__ = "swap" def __init__(self, *, x_period: int = 2, y_period: int = 4, group_size: int = 4): if x_period < 1 or y_period < 1: raise ValueError( f"`swap` effect must have `x_period` and `y_period` both >= 1, " f"but got {x_period} and {y_period} respectively" ) if x_period == y_period: raise ValueError( f"`swap` effect must have unique `x_period` and `y_period` values, " f"but both were {x_period}" ) self.low_period = (min(x_period, y_period) - 1) % group_size + 1 self.high_period = (max(x_period, y_period) - 1) % group_size + 1 self.group_size = group_size def __call__(self, beats): for group in chunks(beats, self.group_size): if len(group) >= self.high_period: (group[self.low_period - 1], group[self.high_period - 1]) = ( group[self.high_period - 1], group[self.low_period - 1], ) yield from group def __eq__(self, other): return isinstance(other, SwapBeats) and (self.low_period, self.high_period) == ( other.low_period, other.high_period, ) class RemapBeats(BaseEffect, metaclass=EffectABCMeta): """ An effect that remaps beats based on a list of target indices. For example, a remap effect with mapping [0, 3, 2, 1] behaves identically to a swap effect with periods 2 and 4. """ __effect_name__ = "remap" def __init__(self, *, mapping: List[int]): if any(m < 0 or m >= len(mapping) for m in mapping): raise ValueError( f"values of `remap` effect with {len(mapping)} values must be within range " f"[0, {len(mapping) - 1}], however the following values fall outside of it: " f"{[m for m in mapping if m < 0 or m >= len(mapping)]}" ) self.mapping = mapping def __call__( self, beats: Iterable[AudioSegment] ) -> Generator[AudioSegment, None, None]: for group in chunks(beats, len(self.mapping)): group_size = len(group) remapped_group = [] for beat_idx in self.mapping: if beat_idx < group_size: remapped_group.append(group[beat_idx]) yield from remapped_group def __eq__(self, other): return isinstance(other, RemapBeats) and self.mapping == other.mapping PKMj(=..#beatmachine-2.1.0.dist-info/LICENSEMIT License Copyright (c) 2018 Joseph Savell Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. PK!HMuSa!beatmachine-2.1.0.dist-info/WHEEL HM K-*ϳR03rOK-J,/RH,szd&Y)r$[)T&UD"PK!Hdܒ$beatmachine-2.1.0.dist-info/METADATAXkO^"=@ff7-a,0Dv]r\n(94 +mi]8܇B9I''VզQt(.ef(*Vb{;:]Uɶ]E8HڒR'-)7-5uvidMTj5v6.pKTӬrr&kSש-n|sig>>,LT%u| ^aeV7>MNMT&w}N=){̪VZAf3=iRe|q~7n|Qg]zY+^Nn%.@ASˌnt}t\j;IOcwQǕN_H];DdW3w*<&2[(;M>?:ԁʲߦ_˯y-(mf^tlUHi7zn1}K5oF]LU궏MBO']q(AT3KՒLV:3GtheGKRmk&%[9lN̒sk)oM$ٶ]:e 2{ƞL5Tj@xځ:&g5XC"+S F ʩ7A鐯5ʲ^?τX7ij*5QӓZ,(PƄC(9̎2mnNib;:q(nt^VUlk i|ϞdՔ qBZfT(7bH.LJQYXm*-^uӸi ܀횦*BT1`@-L>|BUi3jkJrEQއ]lC psSZ>Y!ɲ. TM&BM<B->[Z5)^A)tnKUxP4_a/i- ! eA HxiwZhe&aaŻwum>t_JiRER!t*)TPPl>5_@=EKHj}Ieg+Wݙ,{;DeFc|9|m!~MoSoZyG0,Qߗsǡh'x}ѿιgXeKڙv/]!lehǡx605=‡(M[ї1jW16r>y7#x<܊{:eo|aY *8~Mxc*.[^RÔ풰(h Fuy0RYAoLAVhaӁ-&t0ɄN~)̣l-H&RM MEgF-g,\~' ;tzq_!#U<ԏVc<,uK٫Z_Iku`L lXW@ëGO-a:Q@I%@}hM(vg4]呧0;):LG6V =0ۨ̂cHj/挈x5n>-?PK!Ho8"beatmachine-2.1.0.dist-info/RECORD}Ɏ@}? PL Ē)JTBBAmxۛN41_Nhh$/ZE[ a8F# X%v^frNC1RsB/"pMT/t0ciuk|y:#F@O05x..dǟ֏ۣ>򴿊gFV^F)yD=®ӰS4 '@*4-' df-~c,Br[/D2/޺i:z' p"`9ai^f ~X|