]> git.mar77i.info Git - musicbox/commitdiff
subtract the current counter in the frequency generator. sounds so much better!
authormar77i <mar77i@protonmail.ch>
Wed, 20 Nov 2024 22:20:49 +0000 (23:20 +0100)
committermar77i <mar77i@protonmail.ch>
Wed, 20 Nov 2024 22:44:07 +0000 (23:44 +0100)
music.py
musicbox.py

index 19699f92ff40e945d864d5d182a625e4ff420a78..15e70e4543f897c692c5815820e34644dd92e888 100644 (file)
--- 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:
index a04787c6b6f3febf9d814a40f5239505ce6aa8f2..2330675c8f2821dae2b9effdef931f8cb569a980 100755 (executable)
@@ -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)