Spaces:
Running
Running
CREATE
Browse files
fusion.py
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
|
| 3 |
+
import soundfile
|
| 4 |
+
from pydub import AudioSegment
|
| 5 |
+
from pedalboard import (
|
| 6 |
+
Pedalboard,
|
| 7 |
+
Reverb,
|
| 8 |
+
)
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
class Fusion(AudioSegment):
|
| 12 |
+
class InvalidMusicFileError(Exception):
|
| 13 |
+
def __init__(self, file_name):
|
| 14 |
+
self.file_name = file_name
|
| 15 |
+
super().__init__(f"Invalid music file: {file_name}. File not found or not recognized as a valid music file.")
|
| 16 |
+
|
| 17 |
+
@classmethod
|
| 18 |
+
async def loadSound(cls, inputFile: str):
|
| 19 |
+
"""
|
| 20 |
+
Loads and returns the MP3 or WAV (whichever is found) source sound file.
|
| 21 |
+
Stops program execution if file not found.
|
| 22 |
+
"""
|
| 23 |
+
|
| 24 |
+
if os.path.isfile(inputFile) and inputFile.lower().endswith(('.mp3', '.wav', '.aac', '.ogg', '.flac', '.m4a')):
|
| 25 |
+
return cls.from_file(inputFile, format=inputFile.split(".")[-1])
|
| 26 |
+
else:
|
| 27 |
+
raise cls.InvalidMusicFileError(inputFile)
|
| 28 |
+
|
| 29 |
+
@classmethod
|
| 30 |
+
async def effect8D(
|
| 31 |
+
cls,
|
| 32 |
+
sound,
|
| 33 |
+
panBoundary: int = 100, # Perctange of dist from center that audio source can go
|
| 34 |
+
jumpPercentage: int = 5, # Percentage of dist b/w L-R to jump at a time
|
| 35 |
+
timeLtoR: int = 10000, # Time taken for audio source to move from left to right in ms
|
| 36 |
+
volumeMultiplier: int = 6 # Max volume DB increase at edges
|
| 37 |
+
):
|
| 38 |
+
"""
|
| 39 |
+
Generates the 8d sound effect by splitting the audio into multiple smaller pieces,
|
| 40 |
+
pans each piece to make the sound source seem like it is moving from L to R and R to L in loop,
|
| 41 |
+
decreases volume towards center position to make the movement sound like it is a circle
|
| 42 |
+
instead of straight line.
|
| 43 |
+
"""
|
| 44 |
+
piecesCtoR = panBoundary / jumpPercentage
|
| 45 |
+
|
| 46 |
+
# Total pieces when audio source moves from extreme left to extreme right
|
| 47 |
+
piecesLtoR = piecesCtoR * 2
|
| 48 |
+
|
| 49 |
+
# Time length of each piece
|
| 50 |
+
pieceTime = int(timeLtoR / piecesLtoR)
|
| 51 |
+
|
| 52 |
+
pan = []
|
| 53 |
+
left = -panBoundary # Audio source to start from extreme left
|
| 54 |
+
|
| 55 |
+
while left <= panBoundary: # Until audio source position reaches extreme right
|
| 56 |
+
pan.append(left) # Append the position to pan array
|
| 57 |
+
left += jumpPercentage # Increment to next position
|
| 58 |
+
|
| 59 |
+
# Above loop generates number in range -100 to 100, this converts it to -1.0 to 1.0 scale
|
| 60 |
+
pan = [x / 100 for x in pan]
|
| 61 |
+
|
| 62 |
+
sound8d = sound[0] # Stores the 8d sound
|
| 63 |
+
panIndex = 0 # Index of current pan position of pan array
|
| 64 |
+
|
| 65 |
+
# We loop through the pan array forward once, and then in reverse (L to R, then R to L)
|
| 66 |
+
iteratePanArrayForward = True
|
| 67 |
+
|
| 68 |
+
# Loop through starting time of each piece
|
| 69 |
+
for time in range(0, len(sound) - pieceTime, pieceTime):
|
| 70 |
+
|
| 71 |
+
# time + pieceTime = ending time of piece
|
| 72 |
+
piece = sound[time : time + pieceTime]
|
| 73 |
+
|
| 74 |
+
# If at first element of pan array (Left) then iterate forward
|
| 75 |
+
if panIndex == 0:
|
| 76 |
+
iteratePanArrayForward = True
|
| 77 |
+
|
| 78 |
+
# If at last element of pan array (Right) then iterate backward
|
| 79 |
+
if panIndex == len(pan) - 1:
|
| 80 |
+
iteratePanArrayForward = False
|
| 81 |
+
|
| 82 |
+
# (panBoundary / 100) brings panBoundary to the same scale as elements of pan array i.e. -1.0 to 1.0
|
| 83 |
+
# abs(pan[panIndex]) / (panBoundary / 100) = 1 for extreme left/right and 0 for center
|
| 84 |
+
# abs(pan[panIndex]) / (panBoundary / 100) * volumeMultiplier = volumeMultiplier for extreme left/right and 0 for center
|
| 85 |
+
# Hence, volAdjust = 0 for extreme left/right and volumeMultiplier for center
|
| 86 |
+
volAdjust = volumeMultiplier - (
|
| 87 |
+
abs(pan[panIndex]) / (panBoundary / 100) * volumeMultiplier
|
| 88 |
+
)
|
| 89 |
+
|
| 90 |
+
# Decrease piece volume by volAdjust i.e. max volume at extreme left/right and decreases towards center
|
| 91 |
+
piece -= volAdjust
|
| 92 |
+
|
| 93 |
+
# Pan the piece of sound according to the pan array element
|
| 94 |
+
pannedPiece = piece.pan(pan[panIndex])
|
| 95 |
+
|
| 96 |
+
# Iterates the pan array from left to right, then right to left, then left to right and so on..
|
| 97 |
+
if iteratePanArrayForward:
|
| 98 |
+
panIndex += 1
|
| 99 |
+
else:
|
| 100 |
+
panIndex -= 1
|
| 101 |
+
|
| 102 |
+
# Add this panned piece of sound with adjusted volume to the 8d sound
|
| 103 |
+
sound8d = sound8d + pannedPiece
|
| 104 |
+
|
| 105 |
+
return sound8d
|
| 106 |
+
|
| 107 |
+
@classmethod
|
| 108 |
+
async def effectSlowed(cls, sound, speedMultiplier: float = 0.92 ): # Slowdown audio, 1.0 means original speed, 0.5 half speed etc
|
| 109 |
+
"""
|
| 110 |
+
Increases sound frame rate to slow it down.
|
| 111 |
+
Returns slowed down version of the sound.
|
| 112 |
+
"""
|
| 113 |
+
|
| 114 |
+
soundSlowedDown = sound._spawn(
|
| 115 |
+
sound.raw_data,
|
| 116 |
+
overrides={"frame_rate": int(sound.frame_rate * speedMultiplier)},
|
| 117 |
+
)
|
| 118 |
+
soundSlowedDown.set_frame_rate(sound.frame_rate)
|
| 119 |
+
return soundSlowedDown
|
| 120 |
+
|
| 121 |
+
|
| 122 |
+
@classmethod
|
| 123 |
+
async def effectReverb(
|
| 124 |
+
cls,
|
| 125 |
+
sound,
|
| 126 |
+
roomSize: float = 0.8,
|
| 127 |
+
damping: float = 1,
|
| 128 |
+
width : float = 0.5,
|
| 129 |
+
wetLevel: float = 0.3,
|
| 130 |
+
dryLevel: float= 0.8,
|
| 131 |
+
tempFile: str = "tempWavFileForReverb"
|
| 132 |
+
):
|
| 133 |
+
"""
|
| 134 |
+
Adds reverb effect to the sound.
|
| 135 |
+
"""
|
| 136 |
+
outputFile = tempFile+".wav"
|
| 137 |
+
# Convert the sound to a format usable by the pedalboard library
|
| 138 |
+
with open(outputFile, "wb") as out_f:
|
| 139 |
+
sound.export(out_f, format="wav")
|
| 140 |
+
sound, sampleRate = soundfile.read(outputFile)
|
| 141 |
+
|
| 142 |
+
# Define the reverb settings
|
| 143 |
+
addReverb = Pedalboard(
|
| 144 |
+
[Reverb(room_size=roomSize, damping=damping, width=width, wet_level=wetLevel, dry_level=dryLevel)]
|
| 145 |
+
)
|
| 146 |
+
|
| 147 |
+
# Add the reverb effect to the sound and return
|
| 148 |
+
reverbedSound = addReverb(sound, sample_rate=sampleRate)
|
| 149 |
+
with soundfile.SoundFile(outputFile, "w", samplerate=sampleRate, channels=sound.shape[1]) as f:
|
| 150 |
+
f.write(sound)
|
| 151 |
+
sound = cls.from_wav(outputFile)
|
| 152 |
+
os.remove(outputFile)
|
| 153 |
+
return sound
|
| 154 |
+
|
| 155 |
+
@classmethod
|
| 156 |
+
async def saveSound(cls, sound, outputFile: str = "output"):
|
| 157 |
+
"""
|
| 158 |
+
Save the sound in MP3 format.
|
| 159 |
+
"""
|
| 160 |
+
sound.export(outputFile + ".mp3", format="mp3")
|
| 161 |
+
return f"{outputFile}.mp3"
|