Skip to content

Commit

Permalink
Switch from sounddevice to tcod
Browse files Browse the repository at this point in the history
  • Loading branch information
rhelmot committed Dec 21, 2023
1 parent 8180609 commit b1e37f7
Show file tree
Hide file tree
Showing 3 changed files with 60 additions and 28 deletions.
9 changes: 6 additions & 3 deletions sound/__init__.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
# pylint: disable=redefined-builtin
import sounddevice as sd
#import sounddevice as sd
SAMPLE_RATE = 44100
sd.default.samplerate = SAMPLE_RATE # type: ignore
sd.default.channels = 1 # type: ignore
#sd.default.samplerate = SAMPLE_RATE # type: ignore
#sd.default.channels = 1 # type: ignore
import tcod.sdl.audio as sd
#device = tcod.sdl.audio.open()
#assert device.frequency == SAMPLE_RATE

__all__ = ('tone', 'sample', 'envelope', 'filter', 'instrument', 'notes', 'note', 'asyncplayer')
__version__ = "1.1.1-dev.1"
Expand Down
16 changes: 8 additions & 8 deletions sound/sample.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import wave
import struct

from . import SAMPLE_RATE, sd
from . import SAMPLE_RATE#, sd
from .signal import Signal

class RawData(Signal):
Expand Down Expand Up @@ -31,10 +31,10 @@ def from_file(filename):
fdata = [float(x)/(2**15 - 1) for x in idata]
return RawData(fdata)

@staticmethod
def record(seconds):
"""
Make a new RawData by recording from the microphone.
"""
data = sd.rec(seconds * SAMPLE_RATE, blocking=True)
return RawData(data)
# @staticmethod
# def record(seconds):
# """
# Make a new RawData by recording from the microphone.
# """
# #data = sd.rec(seconds * SAMPLE_RATE, blocking=True)
# return RawData(data)
63 changes: 46 additions & 17 deletions sound/signal.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import numpy
import struct
import wave
import time
import threading

import progressbar

Expand All @@ -12,6 +14,34 @@

__all__ = ('Signal', 'LoopSignal', 'DelaySignal', 'SequenceSignal', 'InvertSignal', 'ConstantSignal', 'MixSignal', 'EnvelopeSignal', 'Purifier', 'SliceSignal', 'ReverseSignal')

class AsyncControl:
def __init__(self, device, signal, length=None):
self.device = device
self.signal = signal
self.stopped = False
self.duration_samples = signal.duration if length is None else length * SAMPLE_RATE

def start(self):
threading.Thread(target=self.generate).start()

def generate(self):
buffer = []
for i in range(int(self.duration_samples)):
if len(buffer) == 2048:
self.device.queue_audio(buffer)
buffer = []
if self.stopped:
buffer = []
break
buffer.append(self.signal.amplitude(i))
self.device.queue_audio(buffer)
while self.device.queued_samples:
time.sleep(0.001)
self.device.close()

def stop(self):
self.stopped = True

class Signal(object):
"""
The base class for all signal objects. Represents the abstract concept of a signal over time,
Expand Down Expand Up @@ -51,29 +81,28 @@ def play(self, length: Optional[float]=None, progress=False):
:param progress: Whether to show a progress bar for rendering
"""
data = self.render(length, progress)
sd.play(data, blocking=True)
device = sd.open()
device.queue_audio(data)
while device.queued_samples:
time.sleep(0.001)
device.close()

def play_async(self):
def play_async(self, length: Optional[float] = None):
"""
Play this signal asynchronously. Return the `sounddevice` stream object for this playback.
The only way you should ever really have to interact with the return value of this function is
to call `.stop()` on it.
to call `.close()` on it.
:param thing: The signal to play
"""
def cb(outdata, frames, time, status): # pylint: disable=unused-argument
nonlocal timer
startframe = timer
for i in range(frames):
outdata[i] = self.amplitude(i+startframe)
timer += frames
if timer >= self.duration:
raise sd.CallbackStop

stream = sd.OutputStream(callback=cb)
timer = 0
stream.start()
return stream
device = sd.open()
control = AsyncControl(device, self, length)
control.start()
return control
# data = self.render(length)
# device = sd.open()
# device.queue_audio(data)
# return device

def write(self, filename, length=None, progress=True):
"""
Expand Down Expand Up @@ -106,7 +135,7 @@ def render(self, length=None, progress=False, clip_warn=True):
duration = 3*SAMPLE_RATE
else:
duration = int(duration)
out = numpy.empty((duration,))
out = numpy.empty((duration,), dtype='float32')

pbar = progressbar.ProgressBar(widgets=['Rendering: ', progressbar.Percentage(), ' ', progressbar.Bar(), ' ', progressbar.ETA()], maxval=duration-1).start() if progress else None

Expand Down

0 comments on commit b1e37f7

Please sign in to comment.