8000 Set pitch by loyaniu · Pull Request #94 · pystage/pystage · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Set pitch #94

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 3 commits into
base: development
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@ tinycss2==1.2.1
tomli==2.0.1
webencodings==0.5.1
windows-curses==2.3.1 # for running tests
soundfile==0.12.1
14 changes: 6 additions & 8 deletions src/pystage/core/_sound.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,13 @@ def pystage_addsound(self, name):

def sound_play(self, name, loop=0):
channel = self.mixer.find_channel()
sound = self.sound_manager.get_sound(name)
sound = self.sound_manager.get_sound(name, self.current_pitch)
if sound is not None:
channel.play(sound, loop)
return channel

def sound_playuntildone(self, name):
sound = self.sound_manager.get_sound(name)
sound = self.sound_manager.get_sound(name, self.current_pitch)
if sound is not None:
self.mixer.find_channel().play(sound, 0)
# time.sleep(sound.get_length())
Expand All @@ -43,11 +43,10 @@ def sound_stopallsounds(self):
self.mixer.stop()

def sound_changeeffectby_pitch(self, value):
# TODO: for pitching there is no ready to use code in pygame. To do so
# we must operate on the audio array itself.
# -360 to 360, 10 is a half-step, 120 an octave
# changes only the speed of the sound
pass
self.current_pitch += value
self.current_pitch = min(360, max(-360, self.current_pitch))

sound_changeeffectby_pitch.opcode = "sound_changeeffectby"
sound_changeeffectby_pitch.param = "EFFECT"
Expand All @@ -66,9 +65,8 @@ def sound_changeeffectby_pan(self, value):
sound_changeeffectby_pan.translation = "sound_effects_pan"

def sound_seteffectto_pitch(self, value):
# TODO: for pitching there is no ready to use code in pygame. To do so
# we must operate on the audio array itself.
pass
self.current_pitch = value
self.current_pitch = min(360, max(-360, self.current_pitch))

sound_seteffectto_pitch.opcode = "sound_seteffectto"
sound_seteffectto_pitch.param = "EFFECT"
Expand Down
56 changes: 52 additions & 4 deletions src/pystage/core/assets.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
from svglib.svglib import svg2rlg
from reportlab.graphics import renderPM
import pystage
import subprocess
import soundfile

_round = lambda v: pygame.Vector2(round(v.x), round(v.y))

Expand Down Expand Up @@ -189,7 +191,7 @@ def __str__(self):
class SoundManager():
def __init__(self, owner):
self.owner = owner
self.sounds = {}
self.sounds: dict[str, Sound] = {}

def add_sound(self, name):
if isinstance(name, str):
Expand All @@ -199,9 +201,13 @@ def add_sound(self, name):
for n in name:
self.add_sound(n)

def get_sound(self, name):
return self.sounds[name].sound

def get_sound(self, name, pitch):
if pitch == 0:
return self.sounds[name].sound
sound = self.sounds[name].get_sound_with_pitch(pitch)
if sound:
os.unlink(self.sounds[name].pitched_file)
return sound

class Sound():
'''
Expand All @@ -224,6 +230,48 @@ def __init__(self, sprite, name):
print("WARNING: MP3 is not supported in pyStage. Use wav or ogg format.")
elif self.file is not None:
self.sound = pygame.mixer.Sound(self.file)
path, suf = self.file.rsplit(".", 1)
self.pitched_file = f"{path}_pitched.{suf}"

def get_sound_with_pitch(self, pitch, keep_length=False):
"""
Pitch from -360 to 360
10 is a half-step
120 is an octave
From: https://en.scratch-wiki.info/wiki/Sound_Effect
Right now the pitch effect works by changing the speed of the sound, although in the future the Scratch Team may change it back to only affecting a sound's pitch.

So I leave a parameter for keeping the length of the audio.
"""
if not self.sound:
print("WARNING: no sound file!")
return None

pitch = max(-360, min(360, pitch))
half_steps = pitch / 10
rate_factor = 2.0 ** (half_steps / 12.0)
original_rate = self.get_framerate()
new_rate = round(original_rate * rate_factor, 5)
tempo = max(0.5, min(100, round(1.0 / rate_factor, 5)))
if keep_length:
parameters = "asetrate={},atempo={}".format(new_rate, tempo)
else:
parameters = "asetrate={}".format(new_rate)
conversion_command = ["ffmpeg", "-i", self.file, "-af", parameters, self.pitched_file, "-y"]

with open(os.devnull, 'rb') as devnull:
p = subprocess.Popen(conversion_command, stdin=devnull, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
p_out, p_err = p.communicate()
if p.returncode != 0:
print(f"WARNING: Can't change pitch of sound file: {self.file}")
return self.sound

return pygame.mixer.Sound(self.pitched_file)

def get_framerate(self):
with soundfile.SoundFile(self.file) as f:
framerate = f.samplerate
return framerate


def __str__(self):
Expand Down
0