diff --git a/examples/audio/pitch.py b/examples/audio/pitch.py new file mode 100644 index 000000000..95636e374 --- /dev/null +++ b/examples/audio/pitch.py @@ -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), + ('RĂ©', 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() diff --git a/kivy/core/audio/__init__.py b/kivy/core/audio/__init__.py index cf5f294a3..adc36008d 100644 --- a/kivy/core/audio/__init__.py +++ b/kivy/core/audio/__init__.py @@ -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'. diff --git a/kivy/core/audio/audio_sdl2.pyx b/kivy/core/audio/audio_sdl2.pyx index be71d990f..2ab569fe6 100644 --- a/kivy/core/audio/audio_sdl2.pyx +++ b/kivy/core/audio/audio_sdl2.pyx @@ -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 frames / 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 = 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, (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 diff --git a/kivy/lib/sdl2.pxi b/kivy/lib/sdl2.pxi index b322f7d38..8b67547b1 100644 --- a/kivy/lib/sdl2.pxi +++ b/kivy/lib/sdl2.pxi @@ -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: