From: mar77i Date: Wed, 20 Nov 2024 22:20:49 +0000 (+0100) Subject: subtract the current counter in the frequency generator. sounds so much better! X-Git-Url: https://git.mar77i.info/?a=commitdiff_plain;h=e0bc71c68b82013159399e8cd08378064079deb9;p=musicbox subtract the current counter in the frequency generator. sounds so much better! --- diff --git a/music.py b/music.py index 19699f9..15e70e4 100644 --- a/music.py +++ b/music.py @@ -67,17 +67,18 @@ class SineWave: class Tone: - # A wave, at a frequency and amplitude - def __init__(self, wave, frequency, amplitude, adsr=None): + # A wave, at a frequency and amplitude started after counter samples. + def __init__(self, wave, frequency, amplitude, counter, adsr=None): self.wave = wave assert not isinstance(frequency, str) self.frequency = frequency self.amplitude = amplitude + self.counter = counter self.adsr = adsr self.signal = True def __call__(self, n): - value = self.wave(self.frequency * n) * self.amplitude + value = self.wave(self.frequency * (n - self.counter)) * self.amplitude if self.adsr is not None: value *= self.adsr(self.signal) return value @@ -90,8 +91,9 @@ class Tone: def release(self): self.signal = False - def push(self): + def push(self, counter): self.signal = True + self.counter = counter class ADSR: diff --git a/musicbox.py b/musicbox.py index a04787c..2330675 100755 --- a/musicbox.py +++ b/musicbox.py @@ -12,7 +12,8 @@ from piano_keys import PianoKeys from visualization import KeyVisualization FPS = 60 -CHUNKS_PER_SECOND = 20 +CHUNKS_PER_SECOND = 5 +MASTER_VOLUME = .05 # expect noise when adding >= 20 voices class ChannelManager: @@ -24,25 +25,20 @@ class ChannelManager: basic_sample_type = {-8: numpy.int8, -16: numpy.int16}[sample_format] self.sample_type = numpy.dtype((basic_sample_type, 2)) self.wave = SineWave(self.sample_rate) - self.amplitude = 2 ** (abs(sample_format) - 1) - 1 + self.amplitude = int( + (2 ** (abs(sample_format) - 1) - 1) * MASTER_VOLUME + ) self.envelope = ADSR.Envelope(s(.2), s(.4), .75, s(.5)) self.tones = {} self.counter = 0 - self.eta = time() + 1 / chunks_per_second + self.eta = None @staticmethod def duplicate_channel(g): return ((e, e) for e in g) - @staticmethod - def queue(channel, *args): - channel.queue( - pygame.sndarray.make_sound( - numpy.fromiter(*args) - ) - ) - def update(self): + t = time() for frequency in { frequency for frequency, tone in self.tones.items() if not tone.has_signal() @@ -52,43 +48,47 @@ class ChannelManager: if self.channel: self.channel = None self.counter = 0 - self.eta = 0 + self.eta = None return if self.channel is None: self.channel = pygame.mixer.Channel(True) if self.channel.get_busy() and self.channel.get_queue() is not None: return - t = time() - print("gening", t, self.eta) - if self.eta is not None and self.eta < t: - print("buffer underrun") - sample_slice = slice(self.counter, self.counter + self.chunk_size) - self.queue( - self.channel, - self.duplicate_channel( - int( - sum(tone(n) for tone in self.tones.values()) - / len(self.tones) - ) - for n in range(sample_slice.start, sample_slice.stop) - ), - self.sample_type + stop_counter = self.counter + self.chunk_size + snd = pygame.sndarray.make_sound( + numpy.fromiter( + self.duplicate_channel( + int(sum(tone(n) for tone in self.tones.values())) + for n in range(self.counter, stop_counter) + ), + self.sample_type + ) ) - self.counter = sample_slice.stop + if self.eta is not None and self.eta < t: + msg_prefix = "likely " if self.channel.get_busy() else "" + print(f"{msg_prefix}buffer underrun", self.eta - t) self.eta = time() + self.chunk_size / self.sample_rate + if not self.channel.get_busy(): + self.channel.play(snd) + else: + self.channel.queue(snd) + self.counter = stop_counter def update_tones(self, frequencies): - have_keys = {frequency for frequency, tone in self.tones.items() if tone.signal} + have_keys = { + frequency for frequency, tone in self.tones.items() if tone.signal + } if frequencies == have_keys: return for frequency in frequencies - have_keys: if frequency in self.tones: - self.tones[frequency].push() + self.tones[frequency].push(self.counter) continue self.tones[frequency] = Tone( self.wave, frequency, self.amplitude, + self.counter, ADSR(self.envelope), ) for frequency in have_keys - frequencies: @@ -97,7 +97,7 @@ class ChannelManager: class MusicBox: def __init__(self): - pygame.mixer.pre_init(buffer=2048) + pygame.mixer.pre_init() pygame.init() assert hasattr(pygame.constants, "FINGERMOTION") self.surf = pygame.display.set_mode(flags=pygame.FULLSCREEN)