From 4c071f6f8ed8085185d27474aaa07096239bc39a Mon Sep 17 00:00:00 2001 From: bilhox <69472620+bilhox@users.noreply.github.com> Date: Sun, 22 Sep 2024 22:33:52 +0200 Subject: [PATCH 01/12] first version --- buildconfig/stubs/gen_stubs.py | 1 + buildconfig/stubs/pygame/__init__.pyi | 1 + buildconfig/stubs/pygame/mixer_music.pyi | 31 +++ src_c/music.c | 340 ++++++++++++++++++++++- src_py/__init__.py | 1 + 5 files changed, 373 insertions(+), 1 deletion(-) diff --git a/buildconfig/stubs/gen_stubs.py b/buildconfig/stubs/gen_stubs.py index da679ac8f8..e41e4b28bf 100644 --- a/buildconfig/stubs/gen_stubs.py +++ b/buildconfig/stubs/gen_stubs.py @@ -69,6 +69,7 @@ "event": ["Event"], "font": ["Font"], "mixer": ["Sound", "Channel"], + "mixer_music": ["Music"], "time": ["Clock"], "joystick": ["Joystick"], "window": ["Window"], diff --git a/buildconfig/stubs/pygame/__init__.pyi b/buildconfig/stubs/pygame/__init__.pyi index a8ecdd7f25..291fcc452f 100644 --- a/buildconfig/stubs/pygame/__init__.pyi +++ b/buildconfig/stubs/pygame/__init__.pyi @@ -53,6 +53,7 @@ from ._debug import print_debug_info as print_debug_info from .event import Event as Event from .font import Font as Font from .mixer import Sound as Sound, Channel as Channel +from .mixer_music import Music as Music from .time import Clock as Clock from .joystick import Joystick as Joystick from .window import Window as Window diff --git a/buildconfig/stubs/pygame/mixer_music.pyi b/buildconfig/stubs/pygame/mixer_music.pyi index 40cf46e2a9..bbff9afd0a 100644 --- a/buildconfig/stubs/pygame/mixer_music.pyi +++ b/buildconfig/stubs/pygame/mixer_music.pyi @@ -19,3 +19,34 @@ def queue(filename: FileLike, namehint: str = "", loops: int = 0) -> None: ... def set_endevent(event_type: int, /) -> None: ... def get_endevent() -> int: ... def get_metadata(filename: Optional[FileLike] = None, namehint: str = "") -> Dict[str, str]: ... + +class Music: + def __init__(self, filename: FileLike, namehint: Optional[str] = "") -> None: ... + + def play(self, loops: int = 0, startpos: float = 0.0, fade_in: float = 0.0) -> None: ... + def stop(self) -> None: ... + def rewind(self) -> None: ... + + @property + def title(self) -> str: ... + @property + def artist(self) -> str: ... + @property + def album(self) -> str: ... + @property + def copyright(self) -> str: ... + + @property + def position(self) -> float: ... + @position.setter + def position(self, value : float) -> None: ... + @property + def duration(self) -> float: ... + @property + def paused(self) -> bool: ... + @property + def volume(self) -> float: ... + @volume.setter + def volume(self, value : float) -> None: ... + @property + def ended(self) -> bool: ... diff --git a/src_c/music.c b/src_c/music.c index b6b733f90c..6fa4c4e42c 100644 --- a/src_c/music.c +++ b/src_c/music.c @@ -32,6 +32,19 @@ #include "mixer.h" +typedef struct{ + PyObject_HEAD + Mix_Music *music; + + float volume; + float position; + int paused; + int ended; + +} pgMusicObject; + +static pgMusicObject *current_music_obj = NULL; + static Mix_Music *current_music = NULL; static Mix_Music *queue_music = NULL; static int queue_music_loops = 0; @@ -218,7 +231,6 @@ music_get_volume(PyObject *self, PyObject *_null) { int volume; MIXER_INIT_CHECK(); - volume = Mix_VolumeMusic(-1); return PyFloat_FromDouble(volume / 128.0); } @@ -572,6 +584,320 @@ static PyMethodDef _music_methods[] = { {NULL, NULL, 0, NULL}}; +//////////////// Music Object ////////////////// + +static void +pgmusic_dealloc(pgMusicObject *self, PyObject *_null) +{ + Py_BEGIN_ALLOW_THREADS + Mix_FreeMusic(self->music); + Py_END_ALLOW_THREADS + + Py_TYPE(self)->tp_free(self); +} + +static PyObject * +pgmusic_get_position(pgMusicObject *self, void *v) +{ + double music_position = 0.0; + + music_position = Mix_GetMusicPosition(self->music); + + // If the music_position is -1.0, we still return this value. + // It's to the user to handle it. + return PyFloat_FromDouble(music_position); +} + +static int +pgmusic_set_position(pgMusicObject *self, PyObject *arg, void *v) +{ + double music_position = PyFloat_AsDouble(arg); + + if(music_position < 0.0) + music_position = 0.0; + + if (self == current_music_obj){ + if(Mix_SetMusicPosition(music_position) != -1){ + self->position = music_position; + } + } + else { + self->position = music_position; + } + + return 0; +} + +static PyObject * +pgmusic_get_duration(pgMusicObject *self, void *v) +{ + double music_duration = 0.0; + music_duration = Mix_MusicDuration(self->music); + // music_duration = Mix_GetMusicLoopLengthTime(self->music); + return PyFloat_FromDouble(music_duration); +} + +static PyObject * +pgmusic_get_paused(pgMusicObject *self, void *v) +{ + MIXER_INIT_CHECK(); + + int paused = self->paused; + + if (self == current_music_obj) + paused = Mix_PausedMusic(); + + return PyBool_FromLong(paused); +} + +static int +pgmusic_set_paused(pgMusicObject *self, PyObject *arg, void *v) +{ + MIXER_INIT_CHECK(); + + int paused = PyObject_IsTrue(arg); + + self->paused = paused; + + if (self == current_music_obj){ + if (paused){ + Mix_PauseMusic(); + self->position = Mix_GetMusicPosition(self->music); + } + else + Mix_ResumeMusic(); + } + + return 0; +} + +static int +pgmusic_set_volume(pgMusicObject *self, PyObject *arg, void *v) +{ + if(!PyFloat_Check(arg)){ + PyErr_SetString(PyExc_TypeError, "the value must be a real number"); + return -1; + } + + self->volume = PyFloat_AsDouble(arg); + if (self->volume < 0.0) + self->volume = 0.0; + else if (self->volume > 1.0) + self->volume = 1.0; + + if (self == current_music_obj){ + Mix_VolumeMusic((int)(self->volume * 128)); + } + + return 0; +} + +static PyObject * +pgmusic_get_volume(pgMusicObject *self, void* closure) +{ + return PyFloat_FromDouble(self->volume); +} + +static PyObject * +pgmusic_get_title(pgMusicObject *self, void *closure) +{ +#ifdef SDL_VERSION_ATLEAST(2, 6, 0) + // This returns the filename if no music title was found + const char *title = Mix_GetMusicTitle(self->music); +#else + const char *title = Mix_GetMusicTitleTag(self->music); +#endif + + return PyUnicode_FromString(title); +} + +static PyObject * +pgmusic_get_album(pgMusicObject *self, void *closure) +{ + return PyUnicode_FromString(Mix_GetMusicAlbumTag(self->music)); +} + +static PyObject * +pgmusic_get_artist(pgMusicObject *self, void *closure) +{ + return PyUnicode_FromString(Mix_GetMusicArtistTag(self->music)); +} + +static PyObject * +pgmusic_get_copyright(pgMusicObject *self, void *closure) +{ + return PyUnicode_FromString(Mix_GetMusicCopyrightTag(self->music)); +} + +static PyObject * +pgmusic_get_ended(pgMusicObject *self, void *closure) +{ + return PyBool_FromLong(self->ended); +} + +static int +pgmusic_init(pgMusicObject *self, PyObject *args, PyObject *kwargs) +{ + Mix_Music *new_music = NULL; + PyObject *obj; + char *namehint = NULL; + static char *kwids[] = {"filename", "namehint", NULL}; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|s", kwids, &obj, + &namehint)) + return -1; + + if (!SDL_WasInit(SDL_INIT_AUDIO)) + return -1; + + new_music = _load_music(obj, namehint); + if (new_music == NULL) // meaning it has an error to return + return -1; + + self->music = new_music; + self->volume = 1.0; + self->paused = 0; + self->ended = 0; + self->position = 0.0; + + return 0; +} + +static void +pgmusic_endmusic_callback(void) +{ + // current_music_obj shouldn't be NULL in theory ? + current_music_obj->ended = 1; +} + +static PyObject * +pgmusic_play(pgMusicObject *self, PyObject *args, PyObject *kwargs) +{ + int loops = 0; + float startpos = 0.0; + float fade_in = 0.0; + int val, volume = 0; + + static char *kwids[] = {"loops", "startpos", "fade_in", NULL}; + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|iff", kwids, &loops, + &startpos, &fade_in)) + return NULL; + + MIXER_INIT_CHECK(); + if (!self->music) + return RAISE(pgExc_SDLError, "music not loaded"); + + Py_BEGIN_ALLOW_THREADS; + + /* Check if any music is currently playing */ + if (current_music_obj != NULL || current_music != NULL){ + /* Stolen code from music_stop */ + /* To prevent the queue_music from playing, free it before stopping. */ + if (current_music && queue_music) { + Mix_FreeMusic(queue_music); + queue_music = NULL; + queue_music_loops = 0; + Mix_HaltMusic(); + } + + if (current_music_obj != self){ + Mix_HaltMusic(); + + current_music_obj->paused = 1; + current_music_obj->position = Mix_GetMusicPosition(current_music_obj->music); + + current_music_obj = self; + } + /* If it's not the case, track the new music */ + } else if (current_music_obj == NULL){ + current_music_obj = self; + } + + Mix_QuerySpec(&music_frequency, &music_format, &music_channels); + + if(self->paused){ + self->paused = 0; + if (startpos == 0.0) + startpos = self->position; + } + + self->ended = 0; + Mix_HookMusicFinished(pgmusic_endmusic_callback); + val = Mix_FadeInMusicPos(self->music, loops, (int)(fade_in * 1000), startpos); + Mix_VolumeMusic((int)(self->volume * 128)); + + Py_END_ALLOW_THREADS; + + if (val == -1) + return RAISE(pgExc_SDLError, SDL_GetError()); + + Py_RETURN_NONE; +} + +static PyObject * +pgmusic_stop(pgMusicObject *self, PyObject *_null) +{ + MIXER_INIT_CHECK(); + + Py_BEGIN_ALLOW_THREADS; + + // Only stop music if it's the one set to playback + if (self == current_music_obj) + Mix_HaltMusic(); + + Py_END_ALLOW_THREADS; + Py_RETURN_NONE; +} + +static PyObject * +pgmusic_rewind(pgMusicObject *self, PyObject *_null) +{ + MIXER_INIT_CHECK(); + + Py_BEGIN_ALLOW_THREADS; + + // Only rewind the music if it's the one set to playback + if (self == current_music_obj) + Mix_RewindMusic(); + + Py_END_ALLOW_THREADS; + Py_RETURN_NONE; +} + +static PyMethodDef _musicobj_methods[] = { + {"play", pgmusic_play, METH_VARARGS | METH_KEYWORDS, ""}, + {"stop", pgmusic_stop, METH_NOARGS, ""}, + {"rewind", pgmusic_rewind, METH_NOARGS, ""}, + {NULL, NULL, 0, NULL} // Sentinel +}; + +static PyGetSetDef _musicobj_getset[] = { + {"position", (getter)pgmusic_get_position, (setter)pgmusic_set_position, "", NULL}, + {"duration", (getter)pgmusic_get_duration, NULL, "", NULL}, + {"paused", (getter)pgmusic_get_paused, (setter)pgmusic_set_paused, "", NULL}, + {"volume", (getter)pgmusic_get_volume, (setter)pgmusic_set_volume, "", NULL}, + {"title", (getter)pgmusic_get_title, NULL, "", NULL}, + {"artist", (getter)pgmusic_get_artist, NULL, "", NULL}, + {"album", (getter)pgmusic_get_album, NULL, "", NULL}, + {"copyright", (getter)pgmusic_get_copyright, NULL, "", NULL}, + {"ended", (getter)pgmusic_get_ended, NULL, "", NULL}, + {NULL, 0, NULL, NULL, NULL} // Sentinel +}; + +static PyTypeObject pgMusic_Type = { + PyVarObject_HEAD_INIT(NULL, 0).tp_name = "pygame.mixer_music.Music", + .tp_basicsize = sizeof(pgMusicObject), + .tp_dealloc = (destructor)pgmusic_dealloc, + .tp_new = PyType_GenericNew, + .tp_init = pgmusic_init, + .tp_getset = _musicobj_getset, + .tp_methods = _musicobj_methods, + .tp_flags = Py_TPFLAGS_DEFAULT, + .tp_doc = "music object" +}; + + + MODINIT_DEFINE(mixer_music) { PyObject *module; @@ -622,5 +948,17 @@ MODINIT_DEFINE(mixer_music) Py_DECREF(module); return NULL; } + + if (PyType_Ready(&pgMusic_Type) < 0) { + return NULL; + } + + Py_INCREF(&pgMusic_Type); + if (PyModule_AddObject(module, "Music", (PyObject *)&pgMusic_Type)) { + Py_DECREF(&pgMusic_Type); + Py_DECREF(module); + return NULL; + } + return module; } diff --git a/src_py/__init__.py b/src_py/__init__.py index bce9fe3ebb..0bdfcedfe3 100644 --- a/src_py/__init__.py +++ b/src_py/__init__.py @@ -266,6 +266,7 @@ def PixelArray(surface): # pylint: disable=unused-argument try: import pygame.mixer_music + from pygame.mixer_music import Music # del pygame.mixer_music # print("NOTE2: failed importing pygame.mixer_music in lib/__init__.py") except (ImportError, OSError): From 8d7873b4d8e95e53f6ce0fbdd4734da0469a2090 Mon Sep 17 00:00:00 2001 From: bilhox <69472620+bilhox@users.noreply.github.com> Date: Sun, 22 Sep 2024 22:35:04 +0200 Subject: [PATCH 02/12] clang format --- src_c/music.c | 66 ++++++++++++++++++++++++++------------------------- 1 file changed, 34 insertions(+), 32 deletions(-) diff --git a/src_c/music.c b/src_c/music.c index 6fa4c4e42c..e56438abda 100644 --- a/src_c/music.c +++ b/src_c/music.c @@ -32,9 +32,8 @@ #include "mixer.h" -typedef struct{ - PyObject_HEAD - Mix_Music *music; +typedef struct { + PyObject_HEAD Mix_Music *music; float volume; float position; @@ -589,11 +588,11 @@ static PyMethodDef _music_methods[] = { static void pgmusic_dealloc(pgMusicObject *self, PyObject *_null) { - Py_BEGIN_ALLOW_THREADS - Mix_FreeMusic(self->music); + Py_BEGIN_ALLOW_THREADS Mix_FreeMusic(self->music); Py_END_ALLOW_THREADS - Py_TYPE(self)->tp_free(self); + Py_TYPE(self) + ->tp_free(self); } static PyObject * @@ -613,11 +612,11 @@ pgmusic_set_position(pgMusicObject *self, PyObject *arg, void *v) { double music_position = PyFloat_AsDouble(arg); - if(music_position < 0.0) + if (music_position < 0.0) music_position = 0.0; - if (self == current_music_obj){ - if(Mix_SetMusicPosition(music_position) != -1){ + if (self == current_music_obj) { + if (Mix_SetMusicPosition(music_position) != -1) { self->position = music_position; } } @@ -659,10 +658,10 @@ pgmusic_set_paused(pgMusicObject *self, PyObject *arg, void *v) self->paused = paused; - if (self == current_music_obj){ - if (paused){ + if (self == current_music_obj) { + if (paused) { Mix_PauseMusic(); - self->position = Mix_GetMusicPosition(self->music); + self->position = Mix_GetMusicPosition(self->music); } else Mix_ResumeMusic(); @@ -674,7 +673,7 @@ pgmusic_set_paused(pgMusicObject *self, PyObject *arg, void *v) static int pgmusic_set_volume(pgMusicObject *self, PyObject *arg, void *v) { - if(!PyFloat_Check(arg)){ + if (!PyFloat_Check(arg)) { PyErr_SetString(PyExc_TypeError, "the value must be a real number"); return -1; } @@ -685,7 +684,7 @@ pgmusic_set_volume(pgMusicObject *self, PyObject *arg, void *v) else if (self->volume > 1.0) self->volume = 1.0; - if (self == current_music_obj){ + if (self == current_music_obj) { Mix_VolumeMusic((int)(self->volume * 128)); } @@ -693,7 +692,7 @@ pgmusic_set_volume(pgMusicObject *self, PyObject *arg, void *v) } static PyObject * -pgmusic_get_volume(pgMusicObject *self, void* closure) +pgmusic_get_volume(pgMusicObject *self, void *closure) { return PyFloat_FromDouble(self->volume); } @@ -753,7 +752,7 @@ pgmusic_init(pgMusicObject *self, PyObject *args, PyObject *kwargs) new_music = _load_music(obj, namehint); if (new_music == NULL) // meaning it has an error to return return -1; - + self->music = new_music; self->volume = 1.0; self->paused = 0; @@ -790,7 +789,7 @@ pgmusic_play(pgMusicObject *self, PyObject *args, PyObject *kwargs) Py_BEGIN_ALLOW_THREADS; /* Check if any music is currently playing */ - if (current_music_obj != NULL || current_music != NULL){ + if (current_music_obj != NULL || current_music != NULL) { /* Stolen code from music_stop */ /* To prevent the queue_music from playing, free it before stopping. */ if (current_music && queue_music) { @@ -800,22 +799,24 @@ pgmusic_play(pgMusicObject *self, PyObject *args, PyObject *kwargs) Mix_HaltMusic(); } - if (current_music_obj != self){ + if (current_music_obj != self) { Mix_HaltMusic(); current_music_obj->paused = 1; - current_music_obj->position = Mix_GetMusicPosition(current_music_obj->music); + current_music_obj->position = + Mix_GetMusicPosition(current_music_obj->music); current_music_obj = self; } - /* If it's not the case, track the new music */ - } else if (current_music_obj == NULL){ + /* If it's not the case, track the new music */ + } + else if (current_music_obj == NULL) { current_music_obj = self; } Mix_QuerySpec(&music_frequency, &music_format, &music_channels); - if(self->paused){ + if (self->paused) { self->paused = 0; if (startpos == 0.0) startpos = self->position; @@ -823,7 +824,8 @@ pgmusic_play(pgMusicObject *self, PyObject *args, PyObject *kwargs) self->ended = 0; Mix_HookMusicFinished(pgmusic_endmusic_callback); - val = Mix_FadeInMusicPos(self->music, loops, (int)(fade_in * 1000), startpos); + val = Mix_FadeInMusicPos(self->music, loops, (int)(fade_in * 1000), + startpos); Mix_VolumeMusic((int)(self->volume * 128)); Py_END_ALLOW_THREADS; @@ -868,20 +870,23 @@ static PyMethodDef _musicobj_methods[] = { {"play", pgmusic_play, METH_VARARGS | METH_KEYWORDS, ""}, {"stop", pgmusic_stop, METH_NOARGS, ""}, {"rewind", pgmusic_rewind, METH_NOARGS, ""}, - {NULL, NULL, 0, NULL} // Sentinel + {NULL, NULL, 0, NULL} // Sentinel }; static PyGetSetDef _musicobj_getset[] = { - {"position", (getter)pgmusic_get_position, (setter)pgmusic_set_position, "", NULL}, + {"position", (getter)pgmusic_get_position, (setter)pgmusic_set_position, + "", NULL}, {"duration", (getter)pgmusic_get_duration, NULL, "", NULL}, - {"paused", (getter)pgmusic_get_paused, (setter)pgmusic_set_paused, "", NULL}, - {"volume", (getter)pgmusic_get_volume, (setter)pgmusic_set_volume, "", NULL}, + {"paused", (getter)pgmusic_get_paused, (setter)pgmusic_set_paused, "", + NULL}, + {"volume", (getter)pgmusic_get_volume, (setter)pgmusic_set_volume, "", + NULL}, {"title", (getter)pgmusic_get_title, NULL, "", NULL}, {"artist", (getter)pgmusic_get_artist, NULL, "", NULL}, {"album", (getter)pgmusic_get_album, NULL, "", NULL}, {"copyright", (getter)pgmusic_get_copyright, NULL, "", NULL}, {"ended", (getter)pgmusic_get_ended, NULL, "", NULL}, - {NULL, 0, NULL, NULL, NULL} // Sentinel + {NULL, 0, NULL, NULL, NULL} // Sentinel }; static PyTypeObject pgMusic_Type = { @@ -893,10 +898,7 @@ static PyTypeObject pgMusic_Type = { .tp_getset = _musicobj_getset, .tp_methods = _musicobj_methods, .tp_flags = Py_TPFLAGS_DEFAULT, - .tp_doc = "music object" -}; - - + .tp_doc = "music object"}; MODINIT_DEFINE(mixer_music) { From be4b3725c702c204bf4f44f2c9334bf5ede00f71 Mon Sep 17 00:00:00 2001 From: bilhox <69472620+bilhox@users.noreply.github.com> Date: Fri, 27 Sep 2024 20:51:12 +0200 Subject: [PATCH 03/12] stub fix + macros --- buildconfig/stubs/pygame/mixer_music.pyi | 2 +- src_c/music.c | 37 ++++++++++++++++++++++-- 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/buildconfig/stubs/pygame/mixer_music.pyi b/buildconfig/stubs/pygame/mixer_music.pyi index bbff9afd0a..e3d1f0e9d7 100644 --- a/buildconfig/stubs/pygame/mixer_music.pyi +++ b/buildconfig/stubs/pygame/mixer_music.pyi @@ -21,7 +21,7 @@ def get_endevent() -> int: ... def get_metadata(filename: Optional[FileLike] = None, namehint: str = "") -> Dict[str, str]: ... class Music: - def __init__(self, filename: FileLike, namehint: Optional[str] = "") -> None: ... + def __init__(self, filename: FileLike, namehint: str = "") -> None: ... def play(self, loops: int = 0, startpos: float = 0.0, fade_in: float = 0.0) -> None: ... def stop(self) -> None: ... diff --git a/src_c/music.c b/src_c/music.c index e56438abda..39dc6de81b 100644 --- a/src_c/music.c +++ b/src_c/music.c @@ -598,6 +598,9 @@ pgmusic_dealloc(pgMusicObject *self, PyObject *_null) static PyObject * pgmusic_get_position(pgMusicObject *self, void *v) { + MIXER_INIT_CHECK(); + +#ifdef SDL_MIXER_VERSION_ATLEAST(2, 6, 0) double music_position = 0.0; music_position = Mix_GetMusicPosition(self->music); @@ -605,11 +608,17 @@ pgmusic_get_position(pgMusicObject *self, void *v) // If the music_position is -1.0, we still return this value. // It's to the user to handle it. return PyFloat_FromDouble(music_position); +#else + return RAISE(PyExc_NotImplementedError, + "SDL_Mixer 2.6.0 is needed to get the position of a music"); +#endif } static int pgmusic_set_position(pgMusicObject *self, PyObject *arg, void *v) { + MIXER_INIT_CHECK(); + double music_position = PyFloat_AsDouble(arg); if (music_position < 0.0) @@ -630,10 +639,17 @@ pgmusic_set_position(pgMusicObject *self, PyObject *arg, void *v) static PyObject * pgmusic_get_duration(pgMusicObject *self, void *v) { + MIXER_INIT_CHECK(); + +#ifdef SDL_MIXER_VERSION_ATLEAST(2, 6, 0) double music_duration = 0.0; music_duration = Mix_MusicDuration(self->music); // music_duration = Mix_GetMusicLoopLengthTime(self->music); return PyFloat_FromDouble(music_duration); +#else + return RAISE(PyExc_NotImplementedError, + "SDL_Mixer 2.6.0 is needed to get the duration of a music"); +#endif } static PyObject * @@ -654,6 +670,8 @@ pgmusic_set_paused(pgMusicObject *self, PyObject *arg, void *v) { MIXER_INIT_CHECK(); +#ifdef SDL_MIXER_VERSION_ATLEAST(2, 6, 0) + int paused = PyObject_IsTrue(arg); self->paused = paused; @@ -666,8 +684,12 @@ pgmusic_set_paused(pgMusicObject *self, PyObject *arg, void *v) else Mix_ResumeMusic(); } - return 0; +#else + PyErr_SetString(PyExc_NotImplementedError, + "SDL_Mixer 2.6.0 is needed for using paused setter"); + return -1; +#endif } static int @@ -802,10 +824,17 @@ pgmusic_play(pgMusicObject *self, PyObject *args, PyObject *kwargs) if (current_music_obj != self) { Mix_HaltMusic(); + /* + Here we can't support this process for SDL_Mixer version lower + than 2.6.0. So if a new music is played, the music will be stopped + and declared as ended. + */ +#ifdef SDL_MIXER_VERSION_ATLEAST(2, 6, 0) + current_music_obj->ended = 0; current_music_obj->paused = 1; current_music_obj->position = Mix_GetMusicPosition(current_music_obj->music); - +#endif current_music_obj = self; } /* If it's not the case, track the new music */ @@ -816,7 +845,9 @@ pgmusic_play(pgMusicObject *self, PyObject *args, PyObject *kwargs) Mix_QuerySpec(&music_frequency, &music_format, &music_channels); - if (self->paused) { + // Resume from where the music paused before switching to the new music + // playback + if (self->paused && !self->ended) { self->paused = 0; if (startpos == 0.0) startpos = self->position; From c92fc74b2638fef859a8c98af411b6b70cbb4128 Mon Sep 17 00:00:00 2001 From: bilhox <69472620+bilhox@users.noreply.github.com> Date: Fri, 27 Sep 2024 20:57:01 +0200 Subject: [PATCH 04/12] stub fix --- buildconfig/stubs/pygame/mixer_music.pyi | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/buildconfig/stubs/pygame/mixer_music.pyi b/buildconfig/stubs/pygame/mixer_music.pyi index e3d1f0e9d7..83c2f44bfd 100644 --- a/buildconfig/stubs/pygame/mixer_music.pyi +++ b/buildconfig/stubs/pygame/mixer_music.pyi @@ -18,15 +18,17 @@ def get_pos() -> int: ... def queue(filename: FileLike, namehint: str = "", loops: int = 0) -> None: ... def set_endevent(event_type: int, /) -> None: ... def get_endevent() -> int: ... -def get_metadata(filename: Optional[FileLike] = None, namehint: str = "") -> Dict[str, str]: ... +def get_metadata( + filename: Optional[FileLike] = None, namehint: str = "" +) -> Dict[str, str]: ... class Music: def __init__(self, filename: FileLike, namehint: str = "") -> None: ... - - def play(self, loops: int = 0, startpos: float = 0.0, fade_in: float = 0.0) -> None: ... + def play( + self, loops: int = 0, startpos: float = 0.0, fade_in: float = 0.0 + ) -> None: ... def stop(self) -> None: ... def rewind(self) -> None: ... - @property def title(self) -> str: ... @property @@ -35,11 +37,10 @@ class Music: def album(self) -> str: ... @property def copyright(self) -> str: ... - @property def position(self) -> float: ... @position.setter - def position(self, value : float) -> None: ... + def position(self, value: float) -> None: ... @property def duration(self) -> float: ... @property @@ -47,6 +48,6 @@ class Music: @property def volume(self) -> float: ... @volume.setter - def volume(self, value : float) -> None: ... + def volume(self, value: float) -> None: ... @property def ended(self) -> bool: ... From 2186d6155ed08337dd8072707ac891fb10ebb3b3 Mon Sep 17 00:00:00 2001 From: bilhox <69472620+bilhox@users.noreply.github.com> Date: Fri, 27 Sep 2024 21:00:17 +0200 Subject: [PATCH 05/12] macro fix --- src_c/music.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src_c/music.c b/src_c/music.c index 39dc6de81b..5e987e8234 100644 --- a/src_c/music.c +++ b/src_c/music.c @@ -600,7 +600,7 @@ pgmusic_get_position(pgMusicObject *self, void *v) { MIXER_INIT_CHECK(); -#ifdef SDL_MIXER_VERSION_ATLEAST(2, 6, 0) +#if SDL_MIXER_VERSION_ATLEAST(2, 6, 0) double music_position = 0.0; music_position = Mix_GetMusicPosition(self->music); @@ -641,7 +641,7 @@ pgmusic_get_duration(pgMusicObject *self, void *v) { MIXER_INIT_CHECK(); -#ifdef SDL_MIXER_VERSION_ATLEAST(2, 6, 0) +#if SDL_MIXER_VERSION_ATLEAST(2, 6, 0) double music_duration = 0.0; music_duration = Mix_MusicDuration(self->music); // music_duration = Mix_GetMusicLoopLengthTime(self->music); @@ -670,7 +670,7 @@ pgmusic_set_paused(pgMusicObject *self, PyObject *arg, void *v) { MIXER_INIT_CHECK(); -#ifdef SDL_MIXER_VERSION_ATLEAST(2, 6, 0) +#if SDL_MIXER_VERSION_ATLEAST(2, 6, 0) int paused = PyObject_IsTrue(arg); @@ -829,7 +829,7 @@ pgmusic_play(pgMusicObject *self, PyObject *args, PyObject *kwargs) than 2.6.0. So if a new music is played, the music will be stopped and declared as ended. */ -#ifdef SDL_MIXER_VERSION_ATLEAST(2, 6, 0) +#if SDL_MIXER_VERSION_ATLEAST(2, 6, 0) current_music_obj->ended = 0; current_music_obj->paused = 1; current_music_obj->position = From 9a3dcc63e049a30a0e63e4dafcd48a0158959ed6 Mon Sep 17 00:00:00 2001 From: bilhox <69472620+bilhox@users.noreply.github.com> Date: Fri, 27 Sep 2024 21:02:45 +0200 Subject: [PATCH 06/12] macro fix 2 --- src_c/music.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src_c/music.c b/src_c/music.c index 5e987e8234..7ea41f6c1c 100644 --- a/src_c/music.c +++ b/src_c/music.c @@ -722,7 +722,7 @@ pgmusic_get_volume(pgMusicObject *self, void *closure) static PyObject * pgmusic_get_title(pgMusicObject *self, void *closure) { -#ifdef SDL_VERSION_ATLEAST(2, 6, 0) +#if SDL_VERSION_ATLEAST(2, 6, 0) // This returns the filename if no music title was found const char *title = Mix_GetMusicTitle(self->music); #else From 7d6015cc1bb671b68b89b9baf7aa1705eb89d31b Mon Sep 17 00:00:00 2001 From: bilhox <69472620+bilhox@users.noreply.github.com> Date: Fri, 27 Sep 2024 21:14:09 +0200 Subject: [PATCH 07/12] more macros --- src_c/music.c | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/src_c/music.c b/src_c/music.c index 7ea41f6c1c..3836cf184e 100644 --- a/src_c/music.c +++ b/src_c/music.c @@ -719,14 +719,18 @@ pgmusic_get_volume(pgMusicObject *self, void *closure) return PyFloat_FromDouble(self->volume); } +/* +This part below might need a macro to reduce the number of lines +*/ + static PyObject * pgmusic_get_title(pgMusicObject *self, void *closure) { -#if SDL_VERSION_ATLEAST(2, 6, 0) +#if SDL_MIXER_VERSION_ATLEAST(2, 6, 0) // This returns the filename if no music title was found const char *title = Mix_GetMusicTitle(self->music); #else - const char *title = Mix_GetMusicTitleTag(self->music); + const char *title = ""; #endif return PyUnicode_FromString(title); @@ -735,19 +739,31 @@ pgmusic_get_title(pgMusicObject *self, void *closure) static PyObject * pgmusic_get_album(pgMusicObject *self, void *closure) { +#if SDL_MIXER_VERSION_ATLEAST(2, 6, 0) return PyUnicode_FromString(Mix_GetMusicAlbumTag(self->music)); +#else + return PyUnicode_FromString(""); +#endif } static PyObject * pgmusic_get_artist(pgMusicObject *self, void *closure) { +#if SDL_MIXER_VERSION_ATLEAST(2, 6, 0) return PyUnicode_FromString(Mix_GetMusicArtistTag(self->music)); +#else + return PyUnicode_FromString(""); +#endif } static PyObject * pgmusic_get_copyright(pgMusicObject *self, void *closure) { +#if SDL_MIXER_VERSION_ATLEAST(2, 6, 0) return PyUnicode_FromString(Mix_GetMusicCopyrightTag(self->music)); +#else + return PyUnicode_FromString(""); +#endif } static PyObject * @@ -797,7 +813,7 @@ pgmusic_play(pgMusicObject *self, PyObject *args, PyObject *kwargs) int loops = 0; float startpos = 0.0; float fade_in = 0.0; - int val, volume = 0; + int val; static char *kwids[] = {"loops", "startpos", "fade_in", NULL}; if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|iff", kwids, &loops, @@ -898,9 +914,9 @@ pgmusic_rewind(pgMusicObject *self, PyObject *_null) } static PyMethodDef _musicobj_methods[] = { - {"play", pgmusic_play, METH_VARARGS | METH_KEYWORDS, ""}, - {"stop", pgmusic_stop, METH_NOARGS, ""}, - {"rewind", pgmusic_rewind, METH_NOARGS, ""}, + {"play", (PyCFunction)pgmusic_play, METH_VARARGS | METH_KEYWORDS, ""}, + {"stop", (PyCFunction)pgmusic_stop, METH_NOARGS, ""}, + {"rewind", (PyCFunction)pgmusic_rewind, METH_NOARGS, ""}, {NULL, NULL, 0, NULL} // Sentinel }; @@ -925,7 +941,7 @@ static PyTypeObject pgMusic_Type = { .tp_basicsize = sizeof(pgMusicObject), .tp_dealloc = (destructor)pgmusic_dealloc, .tp_new = PyType_GenericNew, - .tp_init = pgmusic_init, + .tp_init = (initproc)pgmusic_init, .tp_getset = _musicobj_getset, .tp_methods = _musicobj_methods, .tp_flags = Py_TPFLAGS_DEFAULT, From 6b7c1d95091851dada1ba1ef74b366b425fd78ec Mon Sep 17 00:00:00 2001 From: bilhox <69472620+bilhox@users.noreply.github.com> Date: Fri, 27 Sep 2024 21:33:21 +0200 Subject: [PATCH 08/12] more macros --- src_c/music.c | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/src_c/music.c b/src_c/music.c index 3836cf184e..bb9450bd87 100644 --- a/src_c/music.c +++ b/src_c/music.c @@ -617,7 +617,10 @@ pgmusic_get_position(pgMusicObject *self, void *v) static int pgmusic_set_position(pgMusicObject *self, PyObject *arg, void *v) { - MIXER_INIT_CHECK(); + if (!SDL_WasInit(SDL_INIT_AUDIO)) { + PyErr_SetString(pgExc_SDLError, "mixer not initialized"); + return -1; + } double music_position = PyFloat_AsDouble(arg); @@ -668,7 +671,10 @@ pgmusic_get_paused(pgMusicObject *self, void *v) static int pgmusic_set_paused(pgMusicObject *self, PyObject *arg, void *v) { - MIXER_INIT_CHECK(); + if (!SDL_WasInit(SDL_INIT_AUDIO)) { + PyErr_SetString(pgExc_SDLError, "mixer not initialized"); + return -1; + } #if SDL_MIXER_VERSION_ATLEAST(2, 6, 0) @@ -695,6 +701,11 @@ pgmusic_set_paused(pgMusicObject *self, PyObject *arg, void *v) static int pgmusic_set_volume(pgMusicObject *self, PyObject *arg, void *v) { + if (!SDL_WasInit(SDL_INIT_AUDIO)) { + PyErr_SetString(pgExc_SDLError, "mixer not initialized"); + return -1; + } + if (!PyFloat_Check(arg)) { PyErr_SetString(PyExc_TypeError, "the value must be a real number"); return -1; @@ -784,8 +795,10 @@ pgmusic_init(pgMusicObject *self, PyObject *args, PyObject *kwargs) &namehint)) return -1; - if (!SDL_WasInit(SDL_INIT_AUDIO)) + if (!SDL_WasInit(SDL_INIT_AUDIO)) { + PyErr_SetString(pgExc_SDLError, "mixer not initialized"); return -1; + } new_music = _load_music(obj, namehint); if (new_music == NULL) // meaning it has an error to return @@ -821,6 +834,7 @@ pgmusic_play(pgMusicObject *self, PyObject *args, PyObject *kwargs) return NULL; MIXER_INIT_CHECK(); + if (!self->music) return RAISE(pgExc_SDLError, "music not loaded"); From 420b9d56baa1f13adaf10cd849afb99a2ec82898 Mon Sep 17 00:00:00 2001 From: bilhox <69472620+bilhox@users.noreply.github.com> Date: Fri, 27 Sep 2024 21:38:25 +0200 Subject: [PATCH 09/12] final because not subclassable for now --- buildconfig/stubs/pygame/mixer_music.pyi | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/buildconfig/stubs/pygame/mixer_music.pyi b/buildconfig/stubs/pygame/mixer_music.pyi index 83c2f44bfd..4587c7c489 100644 --- a/buildconfig/stubs/pygame/mixer_music.pyi +++ b/buildconfig/stubs/pygame/mixer_music.pyi @@ -1,4 +1,4 @@ -from typing import Optional, Dict +from typing import Optional, Dict, final from pygame.typing import FileLike @@ -22,6 +22,7 @@ def get_metadata( filename: Optional[FileLike] = None, namehint: str = "" ) -> Dict[str, str]: ... +@final class Music: def __init__(self, filename: FileLike, namehint: str = "") -> None: ... def play( From 500089414ff90d88408a762f543bafdf20763114 Mon Sep 17 00:00:00 2001 From: bilhox <69472620+bilhox@users.noreply.github.com> Date: Fri, 27 Sep 2024 21:46:02 +0200 Subject: [PATCH 10/12] float to double --- src_c/music.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src_c/music.c b/src_c/music.c index bb9450bd87..c589dc5c39 100644 --- a/src_c/music.c +++ b/src_c/music.c @@ -35,8 +35,8 @@ typedef struct { PyObject_HEAD Mix_Music *music; - float volume; - float position; + double volume; + double position; int paused; int ended; From ce3e2399b4d4842c4b823004c4d5f66333959161 Mon Sep 17 00:00:00 2001 From: bilhox <69472620+bilhox@users.noreply.github.com> Date: Fri, 27 Sep 2024 21:51:18 +0200 Subject: [PATCH 11/12] float to double 2 --- src_c/music.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src_c/music.c b/src_c/music.c index c589dc5c39..4930cb9012 100644 --- a/src_c/music.c +++ b/src_c/music.c @@ -824,7 +824,7 @@ static PyObject * pgmusic_play(pgMusicObject *self, PyObject *args, PyObject *kwargs) { int loops = 0; - float startpos = 0.0; + double startpos = 0.0; float fade_in = 0.0; int val; From c0f7718322040eb3ff88c4687bdef78cb69b6fe3 Mon Sep 17 00:00:00 2001 From: bilhox <69472620+bilhox@users.noreply.github.com> Date: Wed, 2 Oct 2024 13:09:09 +0200 Subject: [PATCH 12/12] fadein fadeout --- buildconfig/stubs/pygame/mixer_music.pyi | 7 ++ src_c/music.c | 120 +++++++++++++++++++++-- 2 files changed, 121 insertions(+), 6 deletions(-) diff --git a/buildconfig/stubs/pygame/mixer_music.pyi b/buildconfig/stubs/pygame/mixer_music.pyi index 4587c7c489..5653232611 100644 --- a/buildconfig/stubs/pygame/mixer_music.pyi +++ b/buildconfig/stubs/pygame/mixer_music.pyi @@ -30,6 +30,9 @@ class Music: ) -> None: ... def stop(self) -> None: ... def rewind(self) -> None: ... + + fadein : float + @property def title(self) -> str: ... @property @@ -52,3 +55,7 @@ class Music: def volume(self, value: float) -> None: ... @property def ended(self) -> bool: ... + @property + def fadeout(self) -> float: ... + @fadeout.setter + def fadeout(self, value: float) -> None: ... diff --git a/src_c/music.c b/src_c/music.c index 4930cb9012..9db9692cc5 100644 --- a/src_c/music.c +++ b/src_c/music.c @@ -37,6 +37,8 @@ typedef struct { double volume; double position; + double fadein; + double fadeout; int paused; int ended; @@ -639,16 +641,35 @@ pgmusic_set_position(pgMusicObject *self, PyObject *arg, void *v) return 0; } +#if SDL_MIXER_VERSION_ATLEAST(2, 6, 0) +static double +_get_music_duration(pgMusicObject *obj) +{ + double music_duration = 0.0; + + Mix_MusicType music_type = Mix_GetMusicType(obj->music); + switch (music_type) { + case MUS_OGG: + case MUS_MP3: + music_duration = Mix_MusicDuration(obj->music); + return music_duration; + + default: + return -1.0; + } +} +#endif + static PyObject * pgmusic_get_duration(pgMusicObject *self, void *v) { MIXER_INIT_CHECK(); #if SDL_MIXER_VERSION_ATLEAST(2, 6, 0) - double music_duration = 0.0; - music_duration = Mix_MusicDuration(self->music); - // music_duration = Mix_GetMusicLoopLengthTime(self->music); + + double music_duration = _get_music_duration(self); return PyFloat_FromDouble(music_duration); + #else return RAISE(PyExc_NotImplementedError, "SDL_Mixer 2.6.0 is needed to get the duration of a music"); @@ -783,6 +804,58 @@ pgmusic_get_ended(pgMusicObject *self, void *closure) return PyBool_FromLong(self->ended); } +static PyObject * +pgmusic_get_fadein(pgMusicObject *self, void *closure) +{ + return PyFloat_FromDouble(self->fadein); +} + +static int +pgmusic_set_fadein(pgMusicObject *self, PyObject *value, void *closure) +{ + DEL_ATTR_NOT_SUPPORTED_CHECK("fadein", value); + + self->fadein = PyFloat_AsDouble(value); + + return 0; +} + +static PyObject * +pgmusic_get_fadeout(pgMusicObject *self, void *closure) +{ + return PyFloat_FromDouble(self->fadeout); +} + +static int +pgmusic_set_fadeout(pgMusicObject *self, PyObject *value, void *closure) +{ + DEL_ATTR_NOT_SUPPORTED_CHECK("fadeout", value); + +#if SDL_MIXER_VERSION_ATLEAST(2, 6, 0) + + double music_duration = _get_music_duration(self); + + // It means the filetype doesn't support this feature + // So we don't edit the fadeout + if (music_duration < 0.0) + return 0; + + self->fadeout = PyFloat_AsDouble(value); + double time_remaining = music_duration - Mix_GetMusicPosition(self->music); + + if (self->fadeout > time_remaining) { + self->fadeout = time_remaining; + } + + return 0; +#else + + PyErr_SetString(PyExc_NotImplementedError, + "SDL_Mixer 2.6.0 is needed for using fadeout setter"); + return -1; +#endif +} + static int pgmusic_init(pgMusicObject *self, PyObject *args, PyObject *kwargs) { @@ -809,6 +882,8 @@ pgmusic_init(pgMusicObject *self, PyObject *args, PyObject *kwargs) self->paused = 0; self->ended = 0; self->position = 0.0; + self->fadein = 0.0; + self->fadeout = 0.0; return 0; } @@ -820,6 +895,24 @@ pgmusic_endmusic_callback(void) current_music_obj->ended = 1; } +static void +mixmusic_callback(void *udata, Uint8 *stream, int len) +{ + if (!current_music_obj) + return; + + double current_pos = Mix_GetMusicPosition(current_music_obj->music); + double music_duration = _get_music_duration(current_music_obj); + + if (music_duration < 0.0f) + return; + + double dt_before_fadeout = + music_duration - current_pos - current_music_obj->fadeout; + if (dt_before_fadeout < 0.0) + Mix_FadeOutMusic((int)(current_music_obj->fadeout * 1000)); +} + static PyObject * pgmusic_play(pgMusicObject *self, PyObject *args, PyObject *kwargs) { @@ -883,6 +976,9 @@ pgmusic_play(pgMusicObject *self, PyObject *args, PyObject *kwargs) startpos = self->position; } + if (fade_in != 0.0) + self->fadein = fade_in; + self->ended = 0; Mix_HookMusicFinished(pgmusic_endmusic_callback); val = Mix_FadeInMusicPos(self->music, loops, (int)(fade_in * 1000), @@ -898,15 +994,23 @@ pgmusic_play(pgMusicObject *self, PyObject *args, PyObject *kwargs) } static PyObject * -pgmusic_stop(pgMusicObject *self, PyObject *_null) +pgmusic_stop(pgMusicObject *self, PyObject *args, PyObject *kwargs) { + double fade_out = 0.0; + + static char *kwids[] = {"fade_out", NULL}; + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|f", kwids, &fade_out)) + return NULL; + MIXER_INIT_CHECK(); Py_BEGIN_ALLOW_THREADS; // Only stop music if it's the one set to playback - if (self == current_music_obj) - Mix_HaltMusic(); + if (self == current_music_obj) { + self->fadeout = fade_out; + Mix_FadeOutMusic((int)(self->fadeout * 1000)); + } Py_END_ALLOW_THREADS; Py_RETURN_NONE; @@ -947,6 +1051,10 @@ static PyGetSetDef _musicobj_getset[] = { {"album", (getter)pgmusic_get_album, NULL, "", NULL}, {"copyright", (getter)pgmusic_get_copyright, NULL, "", NULL}, {"ended", (getter)pgmusic_get_ended, NULL, "", NULL}, + {"fadein", (getter)pgmusic_get_fadein, (setter)pgmusic_set_fadein, "", + NULL}, + {"fadeout", (getter)pgmusic_get_fadeout, (setter)pgmusic_set_fadeout, "", + NULL}, {NULL, 0, NULL, NULL, NULL} // Sentinel };