Skip to content

Commit 1a3146b

Browse files
Merge pull request #2 from unexploredtest/audio-fix
Add audio support
2 parents eda38e5 + dac6228 commit 1a3146b

9 files changed

Lines changed: 81 additions & 6 deletions

File tree

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ repos:
1111
hooks:
1212
- id: flake8
1313
args:
14-
- --max-complexity=30
14+
- --max-complexity=50
1515
- --max-line-length=456
1616
- --show-source
1717
- --statistics

cnds/cnds.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ NB_MODULE(cnds, m) {
1717
.def("get_gba_frame", &Nds::getGbaFrame)
1818
.def("get_top_nds_frame", &Nds::getTopNdsFrame)
1919
.def("get_bot_nds_frame", &Nds::getBotNdsFrame)
20+
.def("get_audio_samples", &Nds::getAudioSamples)
21+
.def("get_audio_buffer_number", &Nds::getAudioBufferNumber)
2022

2123
// Save methods
2224
.def("save_state", &Nds::saveState)

cnds/include/nds.hpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ class Nds {
2121
nb::ndarray<nb::numpy, uint8_t> getTopNdsFrame();
2222
nb::ndarray<nb::numpy, uint8_t> getBotNdsFrame();
2323

24+
// Audio methods
25+
nb::ndarray<nb::numpy, int16_t> getAudioSamples(int count);
26+
uint32_t getAudioBufferNumber();
27+
2428
// Savestate methods
2529
void saveState(std::string path);
2630
void loadState(std::string path);

cnds/nds.cpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,23 @@ nb::ndarray<nb::numpy, uint8_t> Nds::getBotNdsFrame() {
6969
}
7070
}
7171

72+
nb::ndarray<nb::numpy, int16_t> Nds::getAudioSamples(int count) {
73+
uint32_t* raw = m_core->spu.getSamples(count);
74+
int16_t* samples = new int16_t[count * 2];
75+
for (int i = 0; i < count; i++) {
76+
samples[i * 2] = (int16_t)(raw[i] & 0xFFFF);
77+
samples[i * 2 + 1] = (int16_t)((raw[i] >> 16) & 0xFFFF);
78+
}
79+
nb::capsule owner(samples, [](void* p) noexcept {
80+
delete[] (int16_t*)p;
81+
});
82+
return nb::ndarray<nb::numpy, int16_t>(samples, {(size_t)count, 2}, owner);
83+
}
84+
85+
uint32_t Nds::getAudioBufferNumber() {
86+
return m_core->spu.getBufferNumber();
87+
}
88+
7289
void Nds::saveState(std::string path) {
7390
m_core->saveStates.setPath(path, m_isGba);
7491
m_core->saveStates.saveState();

externals/NooDS

pynds/audio.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import time
2+
import threading
3+
4+
import sounddevice as sd
5+
import numpy as np
6+
7+
8+
class Audio:
9+
def __init__(self, pynds) -> None:
10+
self._pynds = pynds
11+
12+
self.running = False
13+
14+
def start(self) -> None:
15+
self.stream = sd.OutputStream(samplerate=32768, channels=2, dtype='int16', blocksize=0)
16+
self.running = True
17+
18+
self.audio_thread = threading.Thread(target=self.play_audio)
19+
self.audio_thread.start()
20+
21+
def close(self) -> None:
22+
if (self.running):
23+
self.running = False
24+
self.audio_thread.join()
25+
26+
def play_audio(self):
27+
self.stream.start()
28+
29+
while (self.running):
30+
samples = self._pynds.get_audio(699)
31+
data_to_write = np.ascontiguousarray(samples)
32+
self.stream.write(data_to_write)
33+
34+
time.sleep(0.001)
35+
36+
self.stream.stop()
37+
self.stream.close()

pynds/pynds.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from .memory import Memory
77
from .button import Button
88
from .window import Window
9+
from .audio import Audio
910
from .config import config
1011

1112

@@ -23,6 +24,7 @@ def __init__(self, path: str, save_path: str = "", auto_detect: bool = True, is_
2324
self.button = Button(self._nds)
2425
self.memory = Memory(self._nds)
2526
self.window = Window(self)
27+
self.audio = Audio(self)
2628

2729
def get_is_gba(self) -> bool:
2830
return self.is_gba
@@ -54,6 +56,12 @@ def get_frame_shape(self) -> Tuple[int, int]:
5456
else:
5557
return (NDS[0]*scale, NDS[1]*scale, 4)
5658

59+
def get_audio(self, count: int = 699) -> np.ndarray:
60+
return self._nds.get_audio_samples(count)
61+
62+
def get_audio_buffer_number(self) -> int:
63+
return self._nds.get_audio_buffer_number()
64+
5765
def open_window(self, width: int = 800, height: int = 800) -> None:
5866
if (self.window.running):
5967
self.window.close()
@@ -63,6 +71,15 @@ def open_window(self, width: int = 800, height: int = 800) -> None:
6371
def close_window(self) -> None:
6472
self.window.close()
6573

74+
def open_audio(self) -> None:
75+
if (self.audio.running):
76+
self.audio.close()
77+
78+
self.audio.start()
79+
80+
def close_audio(self) -> None:
81+
self.audio.close()
82+
6683
def render(self) -> None:
6784
if (self.window.running):
6885
self.window.render()

pynds/window.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import pygame
21
import numpy as np
2+
import pygame
33

44

55
class Window:
@@ -13,7 +13,6 @@ def init(self, width: int, height: int) -> None:
1313
pygame.init()
1414
self.screen = pygame.display.set_mode((width, height), pygame.RESIZABLE)
1515
pygame.display.set_caption("PyNDS")
16-
1716
self.running = True
1817

1918
def close(self) -> None:
@@ -137,5 +136,4 @@ def process_frame_nds(self):
137136
surface = pygame.image.frombuffer(frame_bot, (frame_bot.shape[0], frame_bot.shape[1]), 'RGBA')
138137
surface = pygame.transform.scale(surface, (width, height // 2))
139138
self.screen.blit(surface, surface.get_rect(topleft=(0, height // 2)))
140-
141139
pygame.display.flip()

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ def build_extensions(self):
9090

9191
setup(name='pynds',
9292
packages=find_packages(),
93-
install_requires=['numpy', 'pygame'],
93+
install_requires=['numpy', 'pygame', 'sounddevice'],
9494
version=version,
9595
description='Python bindings for NooDS',
9696
author='unexploredtest',

0 commit comments

Comments
 (0)