diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f26e8b4..b43e95f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -11,7 +11,7 @@ repos: hooks: - id: flake8 args: - - --max-complexity=30 + - --max-complexity=50 - --max-line-length=456 - --show-source - --statistics diff --git a/cnds/cnds.cpp b/cnds/cnds.cpp index 511ae1a..50afa4f 100644 --- a/cnds/cnds.cpp +++ b/cnds/cnds.cpp @@ -17,6 +17,8 @@ NB_MODULE(cnds, m) { .def("get_gba_frame", &Nds::getGbaFrame) .def("get_top_nds_frame", &Nds::getTopNdsFrame) .def("get_bot_nds_frame", &Nds::getBotNdsFrame) + .def("get_audio_samples", &Nds::getAudioSamples) + .def("get_audio_buffer_number", &Nds::getAudioBufferNumber) // Save methods .def("save_state", &Nds::saveState) diff --git a/cnds/include/nds.hpp b/cnds/include/nds.hpp index cd4c303..7925d7c 100644 --- a/cnds/include/nds.hpp +++ b/cnds/include/nds.hpp @@ -21,6 +21,10 @@ class Nds { nb::ndarray getTopNdsFrame(); nb::ndarray getBotNdsFrame(); + // Audio methods + nb::ndarray getAudioSamples(int count); + uint32_t getAudioBufferNumber(); + // Savestate methods void saveState(std::string path); void loadState(std::string path); diff --git a/cnds/nds.cpp b/cnds/nds.cpp index da2990d..256366e 100644 --- a/cnds/nds.cpp +++ b/cnds/nds.cpp @@ -69,6 +69,23 @@ nb::ndarray Nds::getBotNdsFrame() { } } +nb::ndarray Nds::getAudioSamples(int count) { + uint32_t* raw = m_core->spu.getSamples(count); + int16_t* samples = new int16_t[count * 2]; + for (int i = 0; i < count; i++) { + samples[i * 2] = (int16_t)(raw[i] & 0xFFFF); + samples[i * 2 + 1] = (int16_t)((raw[i] >> 16) & 0xFFFF); + } + nb::capsule owner(samples, [](void* p) noexcept { + delete[] (int16_t*)p; + }); + return nb::ndarray(samples, {(size_t)count, 2}, owner); +} + +uint32_t Nds::getAudioBufferNumber() { + return m_core->spu.getBufferNumber(); +} + void Nds::saveState(std::string path) { m_core->saveStates.setPath(path, m_isGba); m_core->saveStates.saveState(); diff --git a/externals/NooDS b/externals/NooDS index c0782e6..9490fd9 160000 --- a/externals/NooDS +++ b/externals/NooDS @@ -1 +1 @@ -Subproject commit c0782e6e2e52be0ec8b96e4bbbb6cbbd30441311 +Subproject commit 9490fd916169d84dc4f2270487a7c656b34e040b diff --git a/pynds/audio.py b/pynds/audio.py new file mode 100644 index 0000000..878474e --- /dev/null +++ b/pynds/audio.py @@ -0,0 +1,37 @@ +import time +import threading + +import sounddevice as sd +import numpy as np + + +class Audio: + def __init__(self, pynds) -> None: + self._pynds = pynds + + self.running = False + + def start(self) -> None: + self.stream = sd.OutputStream(samplerate=32768, channels=2, dtype='int16', blocksize=0) + self.running = True + + self.audio_thread = threading.Thread(target=self.play_audio) + self.audio_thread.start() + + def close(self) -> None: + if (self.running): + self.running = False + self.audio_thread.join() + + def play_audio(self): + self.stream.start() + + while (self.running): + samples = self._pynds.get_audio(699) + data_to_write = np.ascontiguousarray(samples) + self.stream.write(data_to_write) + + time.sleep(0.001) + + self.stream.stop() + self.stream.close() diff --git a/pynds/pynds.py b/pynds/pynds.py index cbdae30..2dca1d1 100644 --- a/pynds/pynds.py +++ b/pynds/pynds.py @@ -6,6 +6,7 @@ from .memory import Memory from .button import Button from .window import Window +from .audio import Audio from .config import config @@ -23,6 +24,7 @@ def __init__(self, path: str, save_path: str = "", auto_detect: bool = True, is_ self.button = Button(self._nds) self.memory = Memory(self._nds) self.window = Window(self) + self.audio = Audio(self) def get_is_gba(self) -> bool: return self.is_gba @@ -54,6 +56,12 @@ def get_frame_shape(self) -> Tuple[int, int]: else: return (NDS[0]*scale, NDS[1]*scale, 4) + def get_audio(self, count: int = 699) -> np.ndarray: + return self._nds.get_audio_samples(count) + + def get_audio_buffer_number(self) -> int: + return self._nds.get_audio_buffer_number() + def open_window(self, width: int = 800, height: int = 800) -> None: if (self.window.running): self.window.close() @@ -63,6 +71,15 @@ def open_window(self, width: int = 800, height: int = 800) -> None: def close_window(self) -> None: self.window.close() + def open_audio(self) -> None: + if (self.audio.running): + self.audio.close() + + self.audio.start() + + def close_audio(self) -> None: + self.audio.close() + def render(self) -> None: if (self.window.running): self.window.render() diff --git a/pynds/window.py b/pynds/window.py index a3ee3b8..f9cd52b 100644 --- a/pynds/window.py +++ b/pynds/window.py @@ -1,5 +1,5 @@ -import pygame import numpy as np +import pygame class Window: @@ -13,7 +13,6 @@ def init(self, width: int, height: int) -> None: pygame.init() self.screen = pygame.display.set_mode((width, height), pygame.RESIZABLE) pygame.display.set_caption("PyNDS") - self.running = True def close(self) -> None: @@ -137,5 +136,4 @@ def process_frame_nds(self): surface = pygame.image.frombuffer(frame_bot, (frame_bot.shape[0], frame_bot.shape[1]), 'RGBA') surface = pygame.transform.scale(surface, (width, height // 2)) self.screen.blit(surface, surface.get_rect(topleft=(0, height // 2))) - pygame.display.flip() diff --git a/setup.py b/setup.py index 71c965c..7ac2809 100644 --- a/setup.py +++ b/setup.py @@ -90,7 +90,7 @@ def build_extensions(self): setup(name='pynds', packages=find_packages(), - install_requires=['numpy', 'pygame'], + install_requires=['numpy', 'pygame', 'sounddevice'], version=version, description='Python bindings for NooDS', author='unexploredtest',