Melosynth – ein ziemlich einfacher Synthesizer (116 Codezeilen)

Stellt hier eure Projekte vor.
Internetseiten, Skripte, und alles andere bzgl. Python.
gotridofmyphone
User
Beiträge: 6
Registriert: Mittwoch 15. März 2017, 08:54

Melosynth – ein ziemlich einfacher Synthesizer (116 Codezeilen)

Beitragvon gotridofmyphone » Samstag 18. März 2017, 14:52

Hallo,

eigentlich komme ich aus der Perl-Welt. Es kann nicht schaden, dachte ich mir, auch mal über den Tellerrand zu schauen und etwas in Python zu programmieren. Zufällig zur gleichen Zeit interessiere ich mich auch für elektronische Klangerzeugung. Ich denke nicht, dass mein Interesse soweit reicht, dass ich mir perspektivisch eine zehntausende Euro teure Tonstudio-anlage in mein wohnzimmer stellen werde. Aber es reicht gerade eben aus, um mir – nach einem Eintagsgrundlagenstudium der Klangtheorie, Harmonik etc. – einen ganz einfachen Synthesizer zusammen basteln. Unwahrscheinlich ist, dass ich damit irgendeinen professionellen Toningenieur beeindrucken kann, aber ich bekomme wahrscheinlich ein gutes Gefühl dafür, warum entsprechende Gerätschaften so schweineteuer sind.

Natürlich weiß ich, dass es gute Open-Source-Synthesizer gibt. Sowas versuchen selbst zu programmieren ist spannender, als mit fertigen Programmen rumzuspielen.

Ich bin für ehrliche Kritik hinsichtlich Implementierung und Zweckeignung dieses Moduls immer zu haben.

Here we go:

  1. # -*- coding: utf-8 -*-
  2. import pyaudio
  3. import numpy as np
  4. import time
  5.  
  6. """ This is a very simple synthesizer for single sound design
  7.  
  8. It supports tones composed of fundamental and overtones. Amplitude and or frequency
  9. of each partial can be sine-modulated independently.
  10.  
  11. Nobody expects that little exercise in python programming to be suitable for
  12. serious sound design, do they? Real instruments produce a wide spectrum of
  13. overtones. I am not sure python can handle these numbers with that approach,
  14. because I do not take care of any real-time provisions.
  15.  
  16. Based on http://stackoverflow.com/questions/8299 ... -in-python
  17.  
  18. """
  19.  
  20. fs = 44100       # sampling rate, Hz, must be integer
  21.  
  22. def main():
  23.    
  24.     play_melody(1, ["simple", 440, 3])
  25.     time.sleep(1)
  26.     play_melody(1, ['vibr',220.0, 60]) # a minute of sound with vibrato
  27.    
  28.     stream.stop_stream()
  29.  
  30. # generate samples, note conversion to float32 array
  31. def get_samples (timbre, freq, duration):
  32.      partial_samples = []
  33.      divisor = 0
  34.      iseq = np.arange(fs*duration) # integer sequence 1 .. fs*duration
  35.      for partial_tone in timbre_inventory[timbre]:
  36.          partial_samples.append(partial_tone.render_samples(freq, iseq))
  37.          divisor += partial_tone.share
  38.  
  39.      # mix partials by their weights and return an numpy array ready to
  40.      # play
  41.      return (sum(partial_samples)/divisor).astype(np.float32)
  42.  
  43. class Modulation:
  44.  
  45.     def __init__(self, frequency, base_share, mod_share):
  46.         self.base_share = base_share
  47.         self.mod_share  = mod_share
  48.         self.frequency = frequency
  49.  
  50.     def modulate(self, iseq):
  51.         """ Caution: Not quite sure if that really does what it is supposed to.
  52.              This was programmed by a python learner not fond of trying to
  53.              tell professional sound architects about some new kid in town.
  54.              It is just a very naive way to make a tone more dynamic, spawned
  55.              from my intuition.
  56.              
  57.        [-------|-------|-------|-------] Frequency in intervals per second
  58.           *     *     *     *     *    T
  59.          ***   ***   ***   ***   ***   | ^ mod_share (3)
  60.         ***** ***** ***** ***** *****  | = Modulation intensity in relation
  61.        ******************************* –   to ...
  62.        ******************************* |
  63.        ******************************* |
  64.        ******************************* |
  65.        ******************************* | ^ base_share (6)
  66.        ******************************* _ = Minimum amplitude or frequency
  67.        """
  68.         b = self.base_share
  69.         m = self.mod_share
  70.         f = self.frequency
  71.         return ( m * (np.sin(2*np.pi * iseq * f/fs) + 1) / 2 + b) / (m + b)
  72.  
  73. class Partial:
  74.     def __init__(self, nfactor=1, share=1, deviation=0, am=None, fm=None):
  75.         self.nfactor = nfactor
  76.         self.deviation = deviation
  77.         self.share = share
  78.         self.amplitude_modulation = am
  79.         self.frequency_modulation = fm
  80.  
  81.     def render_samples(self, freq, iseq):
  82.         n = self.nfactor
  83.         d = self.deviation
  84.         s = self.share
  85.  
  86.         if self.amplitude_modulation is not None:
  87.             s *= self.amplitude_modulation.modulate(iseq)
  88.         if self.frequency_modulation is not None:
  89.             freq *= self.frequency_modulation.modulate(iseq)
  90.  
  91.         return np.sin( 2*np.pi * iseq * (n*freq+d) / fs ) * s
  92.  
  93. P = Partial
  94. M = Modulation
  95.  
  96. timbre_inventory = {
  97.     'simple': [P(1)],
  98.     'test': [P(1), P(2,0.13), P(3,0.05)],
  99.     'vibr': [P(1), P(2,0.03,fm=M(30,50,1)), P(3,0.15,am=M(5,350,75))],
  100. }
  101.  
  102. p = pyaudio.PyAudio()
  103. stream = p.open(format=pyaudio.paFloat32,
  104.                 channels=1,
  105.                 rate=fs,
  106.                 output=True)
  107.  
  108. def play_melody(volume, *sounds):
  109.     for sound in sounds:
  110.         stream.write(volume*get_samples(*sound))
  111.    
  112. if __name__ == "__main__":
  113.    main()
  114.  
  115. stream.close()
  116. p.terminate()


Wenn ich dieses Programm laufen lasse, kommen mir vier Fragen in den Sinn. Kann sie mir jemand vielleicht beantworten?:
  1. Warum ist eine Sekunde gefühlt drei mal so kurz wie üblich?
  2. Warum ändert sich der zweite Ton mit der Zeit, tritt das Vibrato zunehmend in den Vordergrund und wird immer höher?
  3. Warum gibt es vor dem Abspielen des zweiten Tons einen buffer underrun? Also teilweise kann ich mir das schon erklären, das ist hier ja keine Echtzeitgeschichte. Aber warum nicht bereits vor dem ersten?
  4. Wie kann ich meine genialen Ergüsse der Klangerzeugung in ein WAV gießen, statt sie mir gleich über Lautsprecher auszugeben? Ah, mit dem Modul wave sollte das gehen, haben erste Recherchen ergeben. Was ich mich allerdings frage ist, wie ich den Framebuffer konfigurieren muss. Manche nehmen 1024 oder so. Ist der Wert beliebig?


Danke,
-- gotridofmyphone
Zuletzt geändert von BlackJack am Samstag 18. März 2017, 15:46, insgesamt 1-mal geändert.
Grund: Quelltext in Python-Codebox-Tags gesetzt.
gotridofmyphone
User
Beiträge: 6
Registriert: Mittwoch 15. März 2017, 08:54

Re: Melosynth – ein ziemlich einfacher Synthesizer (116 Codezeilen)

Beitragvon gotridofmyphone » Samstag 18. März 2017, 23:25

Hier noch ein Test des Synthesizers mit einem Geigenton:

  1. timbre_inventory = {
  2.     ...
  3.     'string': [
  4.            P(1), P(2,0.5), P(3,0.15), P(4,0.03), P(5,0.03), P(6,0.1),
  5.            P(7,0.15), P(8,0.2), P(9,0.01), P(10,0.03), P(11,0.01), P(12,0.02),
  6.            P(13,0.08), P(14,0.04), P(15,0.02), P(16,0.01)
  7.     ],
  8.      ...
  9. }
  10. ...
  11. play_melody(1, ['string', 294.0, 60])


Würde man noch geringste Modulationen applizieren, käme das bestimmt noch näher dran, aber ich habe gerade keine Lust weiter zu tüfteln. Schon jetzt klingt es sehr ähnlich wie eine Geige, daher nenne ich den Test gelungen.

Die obigen Werte habe ich abgelesen aus Abb. links unten im Wikipedia-Artikel zu "Frequenzspektrum". Abschnitt "Amplitudenspektrum eines Audiosignals"(23. September 2016)
gotridofmyphone
User
Beiträge: 6
Registriert: Mittwoch 15. März 2017, 08:54

Re: Melosynth – ein ziemlich einfacher Synthesizer (116 Codezeilen)

Beitragvon gotridofmyphone » Sonntag 19. März 2017, 10:36

Ich bin es noch mal,

man kann hier offenbar nur in der ersten Zeit ein Posting bearbeiten. So denn, gibt es halt ein drittes in Folge bei mir, und zwar das letzte vor etwaigen Beiträgen anderer ;) bzw. bis ich mir obige Fragen selbst erklären kann:

Melosynth auf GitHub
jerch
User
Beiträge: 1584
Registriert: Mittwoch 4. März 2009, 14:19

Re: Melosynth – ein ziemlich einfacher Synthesizer (116 Codezeilen)

Beitragvon jerch » Montag 20. März 2017, 23:17

@gotridofmyphone:
Das kann Python auch ohne dieses neumodische numpy-Zeugs: viewtopic.php?f=11&t=34028 :D
gotridofmyphone
User
Beiträge: 6
Registriert: Mittwoch 15. März 2017, 08:54

Re: Melosynth – ein ziemlich einfacher Synthesizer (116 Codezeilen)

Beitragvon gotridofmyphone » Mittwoch 22. März 2017, 21:03

Hallo jerch,

Numpy ist schneller weil kompiliert, heißt es. Jedenfalls danke für den Link, ich schau mir dein Projekt auch mal zur Inspiration an.

In Vordergrund steht für mich nicht die Melodie, sondern das Klangdesign (auch wenn der Name "melosynth" auf etwas hindeutet, insofern unglücklich gewählt). Es geht also um häufiges additives Überlagern, also Produkte von großen Arrays. Wenn Numpy hält, was es verspricht, sollte es dabei schneller sein als Python-Arrays. Zwar gehört Echtzeit nicht zu meinen Anforderungen, doch lange möchte ich auch nicht warten, bis das Programm den Sound ausspuckt. Allerdings werden meine Audiosamples am Anfang auch so kurz sein, dass das keine Rolle spielen dürfte.

Wobei ich gerade nicht ganz sicher bin, ob Numpy in meinem Fall richtig rechnet. Klar, da klemmt wohl ne riesige Testsuite dahinter, die erfolgreich durchläuft. Daher wird eine mathematische Ursache haben, dass meine Frequenzmodulation exponentiell Geschwindigkeit aufnimmt, statt dafür zu sorgen, dass die Frequenz über die ganze Tonlänge gleichmäßig in einem spezifizierten Korridor oszilliert. Das ist mir zwar nicht so wichtig (zunächst nicht so wichtig wie die Amplitudenmodulation, die ordnungsgemäß funktioniert), aber wär schön, wenn das auch ginge. Hilft wohl nur, mir da noch tüchtig Ahnung anzulesen.

Noch wichtiger ist mir aber das Sekundenproblem, das mir nach wie vor ein Rätsel ist: 44100 Hz ist die Samplingrate, aber ein Ton mit einer spezifizierten Sekunde Länge ist bei mir effektiv nur maximal ne halbe Sekunde lang. Dazu mache ich aber demnächst ein eigenes Thema auf.

Zurück zu „Showcase“

Wer ist online?

Mitglieder in diesem Forum: 0 Mitglieder