Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
wybiral committed Oct 19, 2010
0 parents commit 2198d60
Show file tree
Hide file tree
Showing 15 changed files with 737 additions and 0 deletions.
47 changes: 47 additions & 0 deletions example-01.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
from musical.theory import Note, Scale, Chord
from musical.audio import playback

from timeline import Hit, Timeline

# Define key and scale
key = Note('D3')
scale = Scale(key, 'minor')

# Grab progression chords from scale starting at the octave of our key
progression = Chord.progression(scale, base_octave=key.octave)

time = 0.0 # Keep track of currect note placement time in seconds

timeline = Timeline()

# Add progression to timeline by arpeggiating chords from the progression
for index in [0, 2, 3, 1, 0, 2, 3, 4, 5, 4, 0]:
chord = progression[index]
root, third, fifth = chord.notes
arpeggio = [root, third, fifth, third, root, third, fifth, third]
for i, interval in enumerate(arpeggio):
ts = float(i * 2) / len(arpeggio)
timeline.add(time + ts, Hit(interval, 1.0))
time += 2.0

# Strum out root chord to finish
chord = progression[0]
timeline.add(time + 0.0, Hit(chord.notes[0], 4.0))
timeline.add(time + 0.1, Hit(chord.notes[1], 4.0))
timeline.add(time + 0.2, Hit(chord.notes[2], 4.0))
timeline.add(time + 0.3, Hit(chord.notes[1].transpose(12), 4.0))
timeline.add(time + 0.4, Hit(chord.notes[2].transpose(12), 4.0))
timeline.add(time + 0.5, Hit(chord.notes[0].transpose(12), 4.0))

print "Rendering audio..."

data = timeline.render()

# Reduce volume to 25%
data = data * 0.25

print "Playing audio..."

playback.play(data)

print "Done!"
52 changes: 52 additions & 0 deletions example-02.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import random

from musical.theory import Note, Scale
from musical.audio import effect, playback

from timeline import Hit, Timeline


# Define key and scale
key = Note('E3')
scale = Scale(key, 'harmonic minor')

time = 0.0 # Keep track of currect note placement time in seconds

timeline = Timeline()

note = key

# Semi-randomly queue notes from the scale
for i in xrange(64):
if note.index > 50 or note.index < 24:
# If note goes out of comfort zone, randomly place back at base octave
note = scale.get(random.randrange(4) * 2)
note = note.at_octave(key.octave)
else:
# Transpose the note by some small random interval
note = scale.transpose(note, random.choice((-2, -1, 1, 2)))
length = random.choice((0.125, 0.125, 0.25))
timeline.add(time, Hit(note, length + 0.125))
time += length

# Resolve
note = scale.transpose(key, random.choice((-1, 1, 4)))
timeline.add(time, Hit(note, 0.75)) # Tension
timeline.add(time + 0.5, Hit(key, 4.0)) # Resolution

print "Rendering audio..."

data = timeline.render()

print "Applying Chorus effect..."

data = effect.chorus(data, freq=3.14159)

# Reduce volume to 50%
data = data * 0.5

print "Playing audio..."

playback.play(data)

print "Done!"
30 changes: 30 additions & 0 deletions example-03.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import numpy

from musical.theory import Note, Scale
from musical.audio import source, playback

# Define key and scale
key = Note('C4')
scale = Scale(key, 'major')

note = key
chunks = []
for i in xrange(len(scale)):
third = scale.transpose(note, 2)
chunks.append(source.sine(note, 0.5) + source.square(third, 0.5))
note = scale.transpose(note, 1)
fifth = scale.transpose(key, 4)
chunks.append(source.sine(key, 1.5) + source.square(fifth, 1.5))

print "Rendering audio..."

data = numpy.concatenate(chunks)

# Reduce volume to 50%
data = data * 0.5

print "Playing audio..."

playback.play(data)

print "Done!"
1 change: 1 addition & 0 deletions musical/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import theory, audio
1 change: 1 addition & 0 deletions musical/audio/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import source, effect, encode, playback, save
59 changes: 59 additions & 0 deletions musical/audio/effect.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import source

# TODO: More effects. Distortion, echo, delay, reverb, phaser, pitch shift?
# TODO: Better generalize chorus/flanger (they share a lot of code)

def modulated_delay(data, modwave, dry, wet):
''' Use LFO "modwave" as a delay modulator (no feedback)
'''
out = data.copy()
for i in xrange(len(data)):
index = int(i - modwave[i])
if index >= 0 and index < len(data):
out[i] = data[i] * dry + data[index] * wet
return out


def feedback_modulated_delay(data, modwave, dry, wet):
''' Use LFO "modwave" as a delay modulator (with feedback)
'''
out = data.copy()
for i in xrange(len(data)):
index = int(i - modwave[i])
if index >= 0 and index < len(data):
out[i] = out[i] * dry + out[index] * wet
return out


def chorus(data, freq, dry=0.5, wet=0.5, depth=1.0, delay=25.0, rate=44100):
''' Chorus effect
http://en.wikipedia.org/wiki/Chorus_effect
'''
length = float(len(data)) / rate
mil = float(rate) / 1000
delay *= mil
depth *= mil
modwave = (source.sine(freq, length) / 2 + 0.5) * depth + delay
return modulated_delay(data, modwave, dry, wet)


def flanger(data, freq, dry=0.5, wet=0.5, depth=20.0, delay=1.0, rate=44100):
''' Flanger effect
http://en.wikipedia.org/wiki/Flanging
'''
length = float(len(data)) / rate
mil = float(rate) / 1000
delay *= mil
depth *= mil
modwave = (source.sine(freq, length) / 2 + 0.5) * depth + delay
return feedback_modulated_delay(data, modwave, dry, wet)


def tremolo(data, freq, dry=0.5, wet=0.5, rate=44100):
''' Tremolo effect
http://en.wikipedia.org/wiki/Tremolo
'''
length = float(len(data)) / rate
modwave = (source.sine(freq, length) / 2 + 0.5)
return (data * dry) + ((data * modwave) * wet)

30 changes: 30 additions & 0 deletions musical/audio/encode.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import numpy


def as_uint8(data):
''' Return data encoded as unsigned 8 bit integer
'''
data = (data / 2 + 0.5).clip(0, 1)
return (data * 255).astype(numpy.uint8)


def as_int8(data):
''' Return data encoded as signed 8 bit integer
'''
data = data.clip(-1, 1)
return (data * 127).astype(numpy.int8)


def as_uint16(data):
''' Return data encoded as unsigned 16 bit integer
'''
data = (data / 2 + 0.5).clip(0, 1)
return (data * 65535).astype(numpy.uint16)


def as_int16(data):
''' Return data encoded as signed 16 bit integer
'''
data = data.clip(-1, 1)
return (data * 32767).astype(numpy.int16)

91 changes: 91 additions & 0 deletions musical/audio/playback.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import numpy
import encode


def pygame_play(data, rate=44100):
''' Send audio array to pygame for playback
'''
import pygame
pygame.mixer.init(rate, -16, 1, 1024)
sound = pygame.sndarray.numpysnd.make_sound(encode.as_int16(data))
length = sound.get_length()
sound.play()
pygame.time.wait(int(length * 1000))
pygame.quit()


def pygame_supported():
''' Return True is pygame playback is supported
'''
try:
import pygame
except:
return False
return True


def oss_play(data, rate=44100):
''' Send audio array to oss for playback
'''
import ossaudiodev
audio = ossaudiodev.open('/dev/audio','w')
formats = audio.getfmts()
if ossaudiodev.AFMT_S16_LE in formats:
# Use 16 bit if available
audio.setfmt(ossaudiodev.AFMT_S16_LE)
data = encode.as_int16(data)
elif ossaudiodev.AFMT_U8 in formats:
# Otherwise use 8 bit
audio.setfmt(ossaudiodev.AFMT_U8)
data = encode.as_uint8(data)
audio.speed(rate)
while len(data):
audio.write(data[:1024])
data = data[1024:]
audio.flush()
audio.sync()
audio.close()


def oss_supported():
''' Return True is oss playback is supported
'''
try:
import ossaudiodev
except:
return False
return True


def pyaudio_play(data, rate=44100):
''' Send audio array to pyaudio for playback
'''
import pyaudio
p = pyaudio.PyAudio()
stream = p.open(format=pyaudio.paFloat32, channels=1, rate=rate, output=1)
stream.write(data.astype(numpy.float32).tostring())
stream.close()
p.terminate()


def pyaudio_supported():
''' Return True is pyaudio playback is supported
'''
try:
import pyaudio
except:
return False
return True


def play(data, rate=44100):
''' Send audio to first available playback method
'''
if pygame_supported():
return pygame_play(data)
elif oss_supported():
return oss_play(data)
elif pyaudio_supported():
return pyaudio_play(data)
else:
raise Exception("No supported playback method found")
18 changes: 18 additions & 0 deletions musical/audio/save.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import encode

# TODO: Support other formats and settings


def save_wave(data, path, rate=44100):
''' Save audio data to wave file, currently only 16bit
'''
import wave
fp = wave.open(path, 'w')
fp.setnchannels(1)
fp.setframerate(rate)
fp.setsampwidth(2)
fp.setnframes(len(data))
data = encode.as_int16(data)
fp.writeframes(data.tostring())
fp.close()

Loading

0 comments on commit 2198d60

Please sign in to comment.