Skip to content Skip to sidebar Skip to footer

Is It Possible To Play Two Notes At Once With Pyaudio?

Is it possible to play chords using pyaudio?

Solution 1:

Of course!

Just generate the result and pass it to the player.

This article covers a method to do it.

Here is the example code in case the article at the link changes or goes dead:

import math
import numpy
import pyaudio
import itertools
from scipy import interpolate
from operator import itemgetter

# https://davywybiral.blogspot.com/2010/09/procedural-music-with-pyaudio-and-numpy.html

class Note:

  NOTES = ['c','c#','d','d#','e','f','f#','g','g#','a','a#','b']

  def __init__(self, note, octave=4):
    self.octave = octave
    if isinstance(note, int):
      self.index = note
      self.note = Note.NOTES[note]
    elif isinstance(note, str):
      self.note = note.strip().lower()
      self.index = Note.NOTES.index(self.note)

  def transpose(self, halfsteps):
    octave_delta, note = divmod(self.index + halfsteps, 12)S
    return Note(note, self.octave + octave_delta)

  def frequency(self):
    base_frequency = 16.35159783128741 * 2.0 ** (float(self.index) / 12.0)
    return base_frequency * (2.0 ** self.octave)

  def __float__(self):
    return self.frequency()


class Scale:

  def __init__(self, root, intervals):
    self.root = Note(root.index, 0)
    self.intervals = intervals

  def get(self, index):
    intervals = self.intervals
    if index < 0:
      index = abs(index)
      intervals = reversed(self.intervals)
    intervals = itertools.cycle(self.intervals)
    note = self.root
    for i in range(index):
      note = note.transpose(next(intervals))
    return note

  def index(self, note):
    intervals = itertools.cycle(self.intervals)
    index = 0
    x = self.root
    while x.octave != note.octave or x.note != note.note:
      x = x.transpose(next(intervals))
      index += 1
    return index

  def transpose(self, note, interval):
    return self.get(self.index(note) + interval)


def sine(frequency, length, rate):
  length = int(length * rate)
  factor = float(frequency) * (math.pi * 2) / rate
  return numpy.sin(numpy.arange(length) * factor)

def shape(data, points, kind='slinear'):
    items = points.items()
    sorted(items,key=itemgetter(0))
    keys = list(map(itemgetter(0), items))
    vals = list(map(itemgetter(1), items))
    interp = interpolate.interp1d(keys, vals, kind=kind)
    factor = 1.0 / len(data)
    shape = interp(numpy.arange(len(data)) * factor)
    return data * shape

def harmonics1(freq, length):
  a = sine(freq * 1.00, length, 44100)
  b = sine(freq * 2.00, length, 44100) * 0.5
  c = sine(freq * 4.00, length, 44100) * 0.125
  return (a + b + c) * 0.2

def harmonics2(freq, length):
  a = sine(freq * 1.00, length, 44100)
  b = sine(freq * 2.00, length, 44100) * 0.5
  return (a + b) * 0.2

def pluck1(note):
  chunk = harmonics1(note.frequency(), 2)
  return shape(chunk, {0.0: 0.0, 0.005: 1.0, 0.25: 0.5, 0.9: 0.1, 1.0:0.0})

def pluck2(note):
  chunk = harmonics2(note.frequency(), 2)
  return shape(chunk, {0.0: 0.0, 0.5:0.75, 0.8:0.4, 1.0:0.1})

def chord(n, scale):
  root = scale.get(n)
  third = scale.transpose(root, 2)
  fifth = scale.transpose(root, 4)
  return pluck1(root) + pluck1(third) + pluck1(fifth)

root = Note('A', 3)
scale = Scale(root, [2, 1, 2, 2, 1, 3, 1])

chunks = []
chunks.append(chord(21, scale))
chunks.append(chord(19, scale))
chunks.append(chord(18, scale))
chunks.append(chord(20, scale))
chunks.append(chord(21, scale))
chunks.append(chord(22, scale))
chunks.append(chord(20, scale))
chunks.append(chord(21, scale))

chunks.append(chord(21, scale) + pluck2(scale.get(38)))
chunks.append(chord(19, scale) + pluck2(scale.get(37)))
chunks.append(chord(18, scale) + pluck2(scale.get(33)))
chunks.append(chord(20, scale) + pluck2(scale.get(32)))
chunks.append(chord(21, scale) + pluck2(scale.get(31)))
chunks.append(chord(22, scale) + pluck2(scale.get(32)))
chunks.append(chord(20, scale) + pluck2(scale.get(29)))
chunks.append(chord(21, scale) + pluck2(scale.get(28)))

chunk = numpy.concatenate(chunks) * 0.25

p = pyaudio.PyAudio()
stream = p.open(format=pyaudio.paFloat32, channels=1, rate=44100, output=1)
stream.write(chunk.astype(numpy.float32).tostring())
stream.close()
p.terminate()

And here is a better source for it

Solution 2:

It is possible to play multiple notes at once with pyaudio. In a previous answer to a different question I added harmonics to notes, and added some basic effects. Building off this (and adding a bit of buffering), if you wanted to use notes to build chords instead of harmonics, you could define what you consider a chord relationship. Here I've used basic stacking of thirds in first inversion (or stacked fifths) for the chords, but you could define a more robust system of chords if you wanted too. In any case, here is a code example of how you could do this:

import math  # import needed modulesimport pyaudio  


scale_notes = {
    # pitch standard A440 ie a4 = 440Hz'c': 16.35,
    'C': 17.32,
    'd': 18.35,
    'D': 19.45,
    'e': 20.6,
    'f': 21.83,
    'F': 23.12,
    'g': 24.5,
    'G': 25.96,
    'a': 27.5,
    'A': 29.14,
    'b': 30.87
}

note_names = 'cCdDefFgGaAb'defplaynote(note, chord_style):

    if chord_style == "min":
        chord_tones = [0,3,7]
    elif chord_style == "maj":
        chord_tones = [0,4,7]
    elif chord_style == "five":
        chord_tones = [0,7]
    else:
        chord_tones = [0]

    num_notes = len(chord_tones)

    p = pyaudio.PyAudio()  # initialize pyaudio# sampling rate
    sample_rate = 22050

    LENGTH = 1# seconds to play sound

    frames = int(sample_rate * LENGTH)

    wavedata = ''# generating waves
    stream = p.open(
        format=p.get_format_from_width(1),
        channels=1,
        rate=sample_rate,
        output=True)

    CHUNK = 256

    octave = int(note[1])
    frequencies = []
    for tone in chord_tones:
        chord_note = note_names.index(note[0]) + tone
        if chord_note<12:
            chord_note = note_names[chord_note]
            frequencies.append(scale_notes[chord_note] * (2**(octave + 1)))
            print(frequencies)
        else:
            chord_note = note_names[chord_note-12]
            frequencies.append( scale_notes[chord_note] * (2**(octave + 2)))
            print(frequencies)

    y=0for x inrange(frames//CHUNK):
        n=0
        wavedata=b''while n<CHUNK:
            wave=0for freqs in frequencies:
                wave += math.sin((y) / ((sample_rate / freqs) / math.pi)) * 127 + 128
            wave = wave/num_notes
            wavedata += bytes([int(wave)])
            y+=1
            n+=1

        stream.write(wavedata)

    stream.stop_stream()
    stream.close()
    p.terminate()


song = []
whileTrue:
    song_composing = True
    note = ''while note != 'p':
        note = str(input(
            'Enter note (a-G) (capital for sharp) and an octave (0-8) or any other key to play: '))
        if note[0] in scale_notes:
            chord_style = str(
                input('Enter chord quality (maj, min, five): '))
            song.append((note, chord_style))
            playnote(note, chord_style)

    for chord in song:
        playnote(chord[0], chord[1])
    break

So on the prompts, if you put in d4 min, g4 maj, c4 maj you would get a ii-V-I cadence in C.

Post a Comment for "Is It Possible To Play Two Notes At Once With Pyaudio?"