Skip to content

Commit c172028

Browse files
authored
Merge pull request #1844 from yunline/sound-position
Add mixer.Channel.set_source_location()
2 parents 091f89c + ca08ef2 commit c172028

File tree

5 files changed

+101
-1
lines changed

5 files changed

+101
-1
lines changed

buildconfig/stubs/pygame/mixer.pyi

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ class Channel:
8383
def unpause(self) -> None: ...
8484
def fadeout(self, time: int) -> None: ...
8585
def queue(self, sound: Sound) -> None: ...
86+
def set_source_location(self, angle:float, distance:float) -> None: ...
8687
@overload
8788
def set_volume(self, value: float) -> None: ...
8889
@overload

docs/reST/ref/mixer.rst

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -480,6 +480,21 @@ change the default buffer by calling :func:`pygame.mixer.pre_init` before
480480

481481
.. ## Channel.fadeout ##
482482
483+
.. method:: set_source_location
484+
485+
| :sl:`set the position of a playing channel`
486+
| :sg:`set_source_location(angle,distance) -> None`
487+
488+
Set the position (angle, distance) of a playing channel.
489+
490+
`angle`: Angle is in degrees.
491+
492+
`distance`: Range from 0 to 255.
493+
494+
.. versionadded:: 2.3.0
495+
496+
.. ## Channel.set_source_location ##
497+
483498
.. method:: set_volume
484499

485500
| :sl:`set the volume of a playing channel`

src_c/doc/mixer_doc.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
#define DOC_MIXER_CHANNEL_PAUSE "pause() -> None\ntemporarily stop playback of a channel"
3030
#define DOC_MIXER_CHANNEL_UNPAUSE "unpause() -> None\nresume pause playback of a channel"
3131
#define DOC_MIXER_CHANNEL_FADEOUT "fadeout(time) -> None\nstop playback after fading channel out"
32+
#define DOC_MIXER_CHANNEL_SETSOURCELOCATION "set_source_location(angle,distance) -> None\nset the position of a playing channel"
3233
#define DOC_MIXER_CHANNEL_SETVOLUME "set_volume(value) -> None\nset_volume(left, right) -> None\nset the volume of a playing channel"
3334
#define DOC_MIXER_CHANNEL_GETVOLUME "get_volume() -> value\nget the volume of the playing channel"
3435
#define DOC_MIXER_CHANNEL_GETBUSY "get_busy() -> bool\ncheck if the channel is active"

src_c/mixer.c

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1160,6 +1160,37 @@ chan_unpause(PyObject *self, PyObject *_null)
11601160
Py_RETURN_NONE;
11611161
}
11621162

1163+
static PyObject *
1164+
chan_set_source_location(PyObject *self, PyObject *args)
1165+
{
1166+
int channelnum = pgChannel_AsInt(self);
1167+
Sint16 angle;
1168+
float angle_f;
1169+
Uint8 distance;
1170+
float distance_f;
1171+
PyThreadState *_save;
1172+
1173+
if (!PyArg_ParseTuple(args, "ff", &angle_f, &distance_f))
1174+
return NULL;
1175+
1176+
angle = (Sint16)roundf(fmodf(angle_f, 360));
1177+
distance_f = roundf(distance_f);
1178+
if (0 > distance_f || 256 <= distance_f) {
1179+
return RAISE(PyExc_ValueError,
1180+
"distance out of range, expected (0, 255)");
1181+
}
1182+
distance = (Uint8)distance_f;
1183+
1184+
MIXER_INIT_CHECK();
1185+
_save = PyEval_SaveThread();
1186+
if (!Mix_SetPosition(channelnum, angle, distance)) {
1187+
PyEval_RestoreThread(_save);
1188+
return RAISE(pgExc_SDLError, Mix_GetError());
1189+
}
1190+
PyEval_RestoreThread(_save);
1191+
Py_RETURN_NONE;
1192+
}
1193+
11631194
static PyObject *
11641195
chan_set_volume(PyObject *self, PyObject *args)
11651196
{
@@ -1290,6 +1321,8 @@ static PyMethodDef channel_methods[] = {
12901321
{"pause", (PyCFunction)chan_pause, METH_NOARGS, DOC_MIXER_CHANNEL_PAUSE},
12911322
{"unpause", (PyCFunction)chan_unpause, METH_NOARGS,
12921323
DOC_MIXER_CHANNEL_UNPAUSE},
1324+
{"set_source_location", chan_set_source_location, METH_VARARGS,
1325+
DOC_MIXER_CHANNEL_SETSOURCELOCATION},
12931326
{"set_volume", chan_set_volume, METH_VARARGS, DOC_MIXER_CHANNEL_SETVOLUME},
12941327
{"get_volume", (PyCFunction)chan_get_volume, METH_NOARGS,
12951328
DOC_MIXER_CHANNEL_GETVOLUME},

test/mixer_test.py

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import sys
22
import os
33
import unittest
4+
import time
45
import pathlib
56
import platform
67

7-
from pygame.tests.test_utils import example_path
8+
from pygame.tests.test_utils import example_path, prompt, question
89

910
import pygame
1011
from pygame import mixer
@@ -897,6 +898,55 @@ def todo_test_unpause(self):
897898

898899
self.fail()
899900

901+
def test_set_source_location(self):
902+
ch = mixer.Channel(0)
903+
ch.set_source_location(-3.14, 6.25)
904+
self.assertRaises(ValueError, lambda: ch.set_source_location(0, -1))
905+
self.assertRaises(ValueError, lambda: ch.set_source_location(0, 256.0))
906+
self.assertRaises(TypeError, lambda: ch.set_source_location("", 6.25))
907+
908+
909+
class ChannelInteractiveTest(unittest.TestCase):
910+
__tags__ = ["interactive"]
911+
912+
def tearDown(self):
913+
mixer.quit()
914+
mixer.pre_init(0, 0, 0, 0)
915+
916+
def setUp(self):
917+
mixer.init()
918+
filename = example_path(os.path.join("data", "house_lo.mp3"))
919+
self.snd = mixer.Sound(filename)
920+
921+
def test_set_source_location(self):
922+
prompt("Please wear earphones before the test for set_source_location() starts")
923+
ch = self.snd.play()
924+
angle = 0
925+
distance = 100
926+
while ch.get_busy():
927+
ch.set_source_location(angle, distance)
928+
angle += 1
929+
angle %= 360
930+
time.sleep(0.01)
931+
ans = question("You heard the sound was running around you. Is that correct?")
932+
self.assertTrue(ans)
933+
934+
ch = self.snd.play()
935+
angle = 0
936+
distance = 0
937+
direction = 0
938+
while ch.get_busy():
939+
ch.set_source_location(angle, distance)
940+
if distance == 0 or distance == 255:
941+
direction = 1 - direction
942+
distance += 1 if direction else -1
943+
time.sleep(0.01)
944+
945+
ans = question(
946+
"You heard the distance of the sound was changing. Is that correct?"
947+
)
948+
self.assertTrue(ans)
949+
900950

901951
############################### SOUND CLASS TESTS ##############################
902952

0 commit comments

Comments
 (0)