Merge pull request #4372 from Cheaterman/master

Added pitch shifting to audio_sdl2
This commit is contained in:
Gabriel Pettier 2016-08-22 00:49:41 +02:00 committed by GitHub
commit 18b0332d67
4 changed files with 107 additions and 8 deletions

42
examples/audio/pitch.py Normal file
View File

@ -0,0 +1,42 @@
# encoding: utf8
from kivy.app import App
from kivy.lang import Builder
from kivy.clock import Clock
from kivy.core.audio import SoundLoader
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from sys import version_info
NOTES = (
('Do', 1),
('', 9/8.),
('Mi', 5/4.),
('Fa', 4/3.),
('Sol', 3/2.),
('La', 5/3.),
('Si', 15/8.),
)
class Test(App):
def build(self):
self.sound = SoundLoader.load(
'/usr/lib64/python{}.{}/test/audiodata/pluck-pcm32.wav'
.format(*version_info[0:2])
)
root = BoxLayout()
for octave in range(-2, 3):
for note, pitch in NOTES:
button = Button(text=note)
button.pitch = pitch * 2 ** octave
button.bind(on_release=self.play_note)
root.add_widget(button)
return root
def play_note(self, button):
self.sound.pitch = button.pitch
self.sound.play()
Test().run()

View File

@ -50,10 +50,12 @@ from kivy.core import core_register_libs
from kivy.compat import PY2
from kivy.resources import resource_find
from kivy.properties import StringProperty, NumericProperty, OptionProperty, \
AliasProperty, BooleanProperty
AliasProperty, BooleanProperty, BoundedNumericProperty
from kivy.utils import platform
from kivy.setupconfig import USE_SDL2
from sys import float_info
class SoundLoader:
'''Load a sound, using the best loader for the given file type.
@ -116,6 +118,16 @@ class Sound(EventDispatcher):
to 1.
'''
pitch = BoundedNumericProperty(1., min=float_info.epsilon)
'''Pitch of a sound. 2 is an octave higher, .5 one below. This is only
implemented for SDL2 audio provider yet.
.. versionadded:: 1.9.2
:attr:`pitch` is a :class:`~kivy.properties.NumericProperty` and defaults
to 1.
'''
state = OptionProperty('stop', options=('stop', 'play'))
'''State of the sound, one of 'stop' or 'play'.

View File

@ -28,6 +28,7 @@ Depending the compilation of SDL2 mixer and/or installed libraries:
__all__ = ('SoundSDL2', 'MusicSDL2')
include "../../../kivy/lib/sdl2.pxi"
include "../../../kivy/graphics/common.pxi" # For malloc and memcpy (on_pitch)
from kivy.core.audio import Sound, SoundLoader
from kivy.logger import Logger
@ -36,11 +37,6 @@ from kivy.clock import Clock
cdef int mix_is_init = 0
cdef int mix_flags = 0
# old code from audio_sdl, never used it = unfinished?
#cdef void channel_finished_cb(int channel) nogil:
# with gil:
# print('Channel finished playing.', channel)
cdef mix_init():
cdef int audio_rate = 44100
@ -72,14 +68,13 @@ cdef mix_init():
mix_is_init = -1
return 0
#Mix_ChannelFinished(channel_finished_cb)
mix_is_init = 1
return 1
# Container for samples (Mix_LoadWAV)
cdef class ChunkContainer:
cdef Mix_Chunk *chunk
cdef Mix_Chunk *original_chunk
cdef int channel
def __init__(self):
@ -92,6 +87,9 @@ cdef class ChunkContainer:
Mix_HaltChannel(self.channel)
Mix_FreeChunk(self.chunk)
self.chunk = NULL
if self.original_chunk != NULL:
Mix_FreeChunk(self.original_chunk)
self.original_chunk = NULL
# Container for music (Mix_LoadMUS), one channel only
cdef class MusicContainer:
@ -158,6 +156,26 @@ class SoundSDL2(Sound):
frames = points / channels
return <double>frames / <double>freq
def on_pitch(self, instance, value):
cdef ChunkContainer cc = self.cc
cdef int freq, channels
cdef unsigned short fmt
cdef SDL_AudioCVT cvt
if cc.chunk == NULL:
return
if not Mix_QuerySpec(&freq, &fmt, &channels):
return
SDL_BuildAudioCVT(
&cvt,
fmt, channels, int(freq * self.pitch),
fmt, channels, freq,
)
cvt.buf = <Uint8 *>malloc(cc.original_chunk.alen * cvt.len_mult)
cvt.len = cc.original_chunk.alen
memcpy(cvt.buf, cc.original_chunk.abuf, cc.original_chunk.alen)
SDL_ConvertAudio(&cvt)
cc.chunk = Mix_QuickLoad_RAW(cvt.buf, <Uint32>(cvt.len * cvt.len_ratio))
def play(self):
cdef ChunkContainer cc = self.cc
self.stop()
@ -201,7 +219,10 @@ class SoundSDL2(Sound):
Logger.warning('AudioSDL2: Unable to load {}: {}'.format(
self.filename, Mix_GetError()))
else:
cc.original_chunk = Mix_QuickLoad_RAW(cc.chunk.abuf, cc.chunk.alen)
cc.chunk.volume = int(self.volume * 128)
if self.pitch != 1.:
self.on_pitch(self, self.pitch)
def unload(self):
cdef ChunkContainer cc = self.cc

View File

@ -775,6 +775,30 @@ cdef extern from "SDL_ttf.h":
cdef extern from "SDL_audio.h":
cdef int AUDIO_S16SYS
ctypedef struct SDL_AudioFilter:
pass
ctypedef struct SDL_AudioCVT:
int needed
int src_format
int dst_format
double rate_incr
Uint8 *buf
int len
int len_cvt
int len_mult
double len_ratio
SDL_AudioFilter filters[10]
int filter_index
cdef int SDL_BuildAudioCVT(
SDL_AudioCVT *cvt,
int src_format,
Uint8 src_channels,
int src_rate,
int dst_format,
Uint8 dst_channels,
int dst_rate
)
cdef int SDL_ConvertAudio(SDL_AudioCVT *cvt)
cdef extern from "SDL_mixer.h":
cdef struct Mix_Chunk: