9

I want to do make a platform to play chords like guitar does. For example - to play the E chord it playes [0, 2, 2, 1, 0, 0] (from the Low-E string to the High-E string).

I'm trying to play chords on python by playing all the different strings simultaneously (by using threads).

The problem is that every time I start playing the next strings, it seems that the last string stops playing, and that the new one replaces it. So all I hear after playing a chord is the highest string (the last one).

Do I use the threads not correctly? Or is it a problem with the current functions? Or maybe it's the winsound.Beep() function that canwt handle these kind of things?

This is my code:

from winsound import Beep
import threading
import time


def play(freq, dur):
    Beep(round(freq), round(dur))


def get_freq(string, fret):
    a3_freq = 220
    half_tone = 2 ** (1 / 12)

    higher_from_a3_by = 5 * (string - 2) + fret
    if string > 4:
        higher_from_a3_by += 1
    return a3_freq * half_tone ** higher_from_a3_by


def strum(string, fret, time):
    freq = get_freq(string, fret)
    t = threading.Thread(target=play, args=(freq, time))
    t.start()
    return t


def chord(frets, dur):
    threads = []
    for i in range(6):
        if frets[i] != -1:
            threads.append(strum(i + 1, frets[i], dur))
    for t in threads:
        t.join()


chord((-1, 0, 2, 2, 2, 0), 1000) # plays the A chord for one second, for example.

From what I've cheacked, the play() and get_freq() functions donwt have any problem.

So what is the problem, and how can I fix it?

Edit:

I've tried it in C# but it didn't work either. Is this the right way of starting threads in C#?

foreach (Thread t in threads)
    t.Start();
foreach (Thread t in threads)
    t.Join();
Matthieu Brucher
  • 21,634
  • 7
  • 38
  • 62
Tom
  • 129
  • 2
  • 8
  • 1
    Python does not support true parallel execution of threads *within one interpreter instance*. This is because the underlying code of the Python interpreter is partly written in C, and there are some issues with making the code thread-safe. In order to achieve this, you would need to start up another Python interpreter and start a thread there. Then your OS handles the parallel execution of the threads. I think, though, that starting the notes at the exact same time could be a tad tricky. Good luck! – Ukimiku Oct 29 '16 at 13:56
  • Thank you for the quick response, I'll try it! Do you think that in C# I could do the same and it will work? That the problem is only in python? – Tom Oct 29 '16 at 14:50
  • I don't know how things are in C#. I suppose it depends on whether or not the note playing software is coded in a thread-safe way. You would need to check the documentation. Regards – Ukimiku Oct 29 '16 at 15:32
  • One option is to put the chord in a MOD/XM/IT file and play it with [BASS](http://www.un4seen.com/). I see there is a [Python module for BASS](https://sourceforge.net/projects/pybass/) but haven't tried it myself. (And, a blatant plug for [OpenMPT](http://openmpt.org/) as a good way to make such files!) – cxw Oct 29 '16 at 20:34
  • Using threads in this way isn't a good in any language, because the underlying threading system on most OS's won't guarantee anything about when it get executed. Another problem might also be the beep modul, which I don't know, might only support playing one note at a time. You should make a simple synthesizer implementation that handle say up to N frequencies at some given duration, and then have a function to build up the samples based on this information. – J. P. Petersen Oct 29 '16 at 20:46
  • Hi J. P. Petersen, I didn't realy understand your suggestion, can you please explain it to me? cxw thanks for your suggestion, but It seems too complicated for me, for creating a new mod file for every code i play, and it may even take some calculation time and i want the chords to run smoothly and with no delays – Tom Oct 29 '16 at 21:13
  • [Ukimiku](http://stackoverflow.com/users/6804704/ukimiku) I tried Doing it parallel on different python files (I made 1 file per guitar-string, 6 files at all) but it doesn't seem to work... – Tom Oct 30 '16 at 22:07
  • A `Beep()` function sounds like the wrong tool for the task of playing chords. Have you considered using [PyAudio](http://people.csail.mit.edu/hubert/pyaudio/) instead? – blubberdiblub Apr 29 '17 at 20:56

3 Answers3

1

I would really suggest not using threads for this, it is not necessary and very convoluted to solve your polyphony problem. An alternative solution could be to have access to the samples of the currently playing string and simply summing any newly playing strings to it. HOWEVER seeing as you are using winsound this is probably not possible. So I would suggest looking at some other way of playing and/or generating sound using python a quick search shows this

Rampartisan
  • 427
  • 6
  • 19
1

Gevent would make this much easier. Though still technically single threaded, context switching is so fast it would make it appear simultaneous. You can even use GIPC for multithreaded bi-directional multiprocess communication.

However I think your problem is much simpler. Your issue is the windsound module. Beep is not asynchoronous. So that is your limitation. You need to use

winsound.SND_ASYNC

This will allow you to send multiple events to play simultaneously.

eatmeimadanish
  • 3,809
  • 1
  • 14
  • 20
0

Python does not support true parallel execution of threads. Multithreading with python will spawn a new process. However you would be unable to access the process to manage it once its running so the timing might be off if you spawned up a new process to execute.

However you could make a series of libraries for each underlying note and use a coroutine library like gevent to manage the timing and execution of these notes to combine into a chord.

castaway2000
  • 306
  • 5
  • 21