python-music-gen: Ambient hypnotic evolving track

The notes for the track “Ambient Hypnotic” were generated using python-music-gen

You can view the track @ https://soundcloud.com/blackaura/ambient-hypnotic

The MIDI tracks were imported into Ableton Live and software synthesizers and effects were applied.

'''
pymusicgen1 Project
'''
import sys
sys.path.append('../../')
from midiutil.TrackGen import LoopingArray
from midiutil.MidiGenerator import MidiGenerator
midiGenerator = MidiGenerator(tempo=120)
scale = reduce(
    lambda x, y: x + y,
    [
        [y + (12 * x) + 36 + 12
        for y in [0, 2, 0, 7, 10, 7, 0, 8]] for x in range(3)
    ]
)
chordscale = reduce(
    lambda x, y: x + y,
    [
        [y + (12 * x) + 36
        for y in [0, 2, 5, 7, 10, 7, 14, 8]] for x in range(5)
    ]
)
pos = 0
tracks = {
    '00.running notes': {
        'notearrays': [
            {
                'beat': LoopingArray(
                    [(1, 1), (0.5, 0.5), (1.0, 1.0), (0.5, 0.5), (1.5, 1.5)]
                ),
                'notearray': LoopingArray(
                   [item for sublist in
                       [[[scale[(10 + ((x + y))) % len(scale)]] for x in range(10)] for y in range(30)]
                       for item in sublist]
                ),
                'velocities': LoopingArray(
                    [100, 90, 110, 70, 80]
                )
            },
        ]
    },
    '01. Chords 1': {
        'notearrays': [
            {
                'beat': LoopingArray(
                    [(0.5, 0.25), (1.0, 0.5), ]
                ),
                'notearray': LoopingArray(
                    [
                        [chordscale[x]] for x in [10, 12, 14, 16]
                    ]
                ),
                'velocities': LoopingArray(
                    [100]
                )
            },
            {
                'beat': LoopingArray(
                    [(0.5, 0.25), (1.0, 0.5), ]
                ),
                'notearray': LoopingArray(
                    [
                        [chordscale[x]] for x in [20, 22, 21]
                    ]
                ),
                'velocities': LoopingArray(
                    [100]
                )
            },
            {
                'beat': LoopingArray(
                    [(0.5, 0.25), (1.0, 0.5), ]
                ),
                'notearray': LoopingArray(
                    [
                        [chordscale[x]] for x in [25, 24, 23, 22, 24]
                    ]
                ),
                'velocities': LoopingArray(
                    [100]
                )
            }
        ],
    },
    '02. Notes 1': {
        'notearrays': [
            {
                'beat': LoopingArray(
                    [(4.0, 4.0), (8.0, 8.0), ]
                ),
                'notearray': LoopingArray(
                    [
                        [chordscale[x]] for x in [
                            1, 5, 8, 3, 8, 6, 3, 7, 11, 15,
                            20, 4, 15, 5, 18, 6, 10, 11, 4
                        ]
                    ]
                ),
                'velocities': LoopingArray(
                    [127, 110, 106]
                )
            },
        ]
    },
    '03. Notes 2': {
        'notearrays': [
            {
                'beat': LoopingArray(
                    [
                        (4.0, 1.0), (2.0, 1.0),
                        (4.0, 1.0), (4.0, 1.0),
                        (8.0, 1.0), (2.0, 1.0),
                    ]
                ),
                'notearray': LoopingArray(
                    [
                        [chordscale[x + 20]] for x in [
                            1, 2, 3, 8, 9, 10, 5, 6, 7, 13, 14, 15
                        ]
                    ]
                ),
                'velocities': LoopingArray(
                    [127, 110, 106]
                )
            },
        ]
    },
    '04. CHORZ': {
        'notearrays': [
            {
                'beat': LoopingArray(
                    [
                        (4.0, 4.0), (2.0, 2.0),
                        (4.0, 4.0), (4.0, 4.0),
                        (8.0, 8.0), (2.0, 2.0),
                    ]
                ),
                'notearray': LoopingArray(
                    [
                        [chordscale[x + 14]] for x in [
                            0, 4, 8, 3, 7, 11, 13
                        ]
                    ]
                ),
                'velocities': LoopingArray(
                    [127, 110, 106]
                )
            },
            {
                'beat': LoopingArray(
                    [
                        (4.0, 4.0), (2.0, 2.0),
                        (4.0, 4.0), (4.0, 4.0),
                        (8.0, 8.0), (2.0, 2.0),
                    ]
                ),
                'notearray': LoopingArray(
                    [
                        [chordscale[x + 10]] for x in [
                            0, 4, 8, 3, 7, 11, 13
                        ]
                    ]
                ),
                'velocities': LoopingArray(
                    [127, 110, 106]
                )
            },
        ]
    },
}
i = 0
keys = tracks.keys()
keys.sort()
print keys
for track in keys:
    print 'processing %s' % track
    notearrays = tracks[track]['notearrays']
    for n in notearrays:
        beat = n['beat']
        notearray = n['notearray']
        velocities = n['velocities']
        midiGenerator.add_track(
            i,
            pos,
            beat=beat,
            notes=notearray,
            velocities=velocities,
            length=1024)
    i += 1
midiGenerator.write()
Share

PHP parsing library in Python

Project hosted at BitBucket

pyparsephp is a set of libraries which can be used to parse PHP files. The library is designed to be used in PHP source code analysis tools.

The library files can also be used as standalone programs.

Visit the project page for more information

Share

str.startswith is slower than array slicing

def test1():
    test_string = 'abcdef'
    if test_string[:3] == 'abc':
        q = 1
def test2():
    test_string = 'abcdef'
    if test_string.startswith('abc'):
        q = 1
if __name__ == '__main__':
    import timeit
    print 'time1:', \
      (timeit.timeit("test1()", setup="from __main__ import test1", number=100000000))
    print 'time2:', \
      (timeit.timeit("test2()", setup="from __main__ import test2", number=100000000))

Result:

time1: 22.621565336
time2: 33.7879341707

Each transaction is ~1.11663688347e-7 slower so “slower” is relative to your problem size!

Share

Using IPython to access your libraries easily

IPython is a powerful Python shell which can be used to incorporate your libraries into a single console application. I find this extremely useful when I need to execute and experiment with functions from several small utility libraries that I have written. The first thing that you should do is to create a new IPython profile

ipython profile create new_profile_name

IPython will print the directory location where the new profile was created.


[ProfileCreate] Generating default config file: u'C:\\Users\\a\\.ipython\\profile_new_profile_name\\ipython_config.py'
[ProfileCreate] Generating default config file: u'C:\\Users\\a\\.ipython\\profile_new_profile_name\\ipython_qtconsole_config.py'

You can load this profile by executing:


ipython --profile=new_profile_name

What is great about this is that you can move the profile directory anywhere on your hard drive and then you can run IPython using that profile. This makes it possible to share the profile while working in a team. So if you move the folder profile_new_profile_name to a directory /myprojects/project_1, you can:


cd /myprojects/project_1
ipython --profile=new_profile_name

In order to load your libraries during iPython’s startup, navigate to the folder profile_new_profile_name/startup and add any startup scripts that you require there. The files here are loaded in alphabetical order.

The following startup script will load your libraries into IPython console:

import sys
print 'Importing libaries'
sys.path.append('src/lib/your_nice_library_1')
sys.path.append('../your_other_libary')
from your_nice_library_1 import *
from your_other_libary import *

Once you load IPython with this new profile, you should see:


ipython --profile=new_profile_name--colors=linux
Python 2.7.3 (default, Apr 10 2012, 23:24:47) [MSC v.1500 64 bit (AMD64)]
Type "copyright", "credits" or "license" for more information.
IPython 0.13 -- An enhanced Interactive Python.
IPython profile: new_profile_name
Importing libaries

Share

Python Music Generator

Simple library to generate midi patterns from numbers. This library can be used to build generative music tools.

Download from http://code.google.com/p/python-music-gen/

Midi examples

http://www.black-aura.com/resources/python-music-gen/arptest.mid

http://www.black-aura.com/resources/python-music-gen/bassline.mid

http://www.black-aura.com/resources/python-music-gen/chords.mid

http://www.black-aura.com/resources/python-music-gen/chords2.mid

MP3 examples (imported MIDI in Ableton Live and added instruments)

http://www.black-aura.com/resources/python-music-gen/arps.mp3

Code Examples

View the examples under /music to start creating your own music!

Documentation

Creating a MIDI file

from midiutil.MidiGenerator import MidiGenerator
midiGenerator = MidiGenerator(tempo=105)
midiGenerator.write()

This code will write an empty midi file. The midi file name will be the python_file_name.mid.

A simple loop

The following code will generate a loop with notes 60,62 and 64 playing in sequence. The length and distance between notes will be 1/2 a beat. The velocities will loop as follows: 100, 90, 100, 90, etc.

from midiutil.MidiGenerator import MidiGenerator
from midiutil.TrackGen import LoopingArray
midiGenerator = MidiGenerator(tempo=105)
# Create a looping array of notes 60, 62 and 64 (C, D, E)
notes = LoopingArray([[60],[62],[64]])
# Create a looping array of beats (note duration, note length)
beats = LoopingArray([(0.5,0.5)])
# Create a looping array of velocities
velocities = LoopingArray([100,90])
midiGenerator.add_track(0, 0, beat=beats, notes=notes, velocities=velocities, length=16)
midiGenerator.write()

A slightly more complex loop

The following loop will play multiple notes with a varying beat.

from midiutil.MidiGenerator import MidiGenerator
from midiutil.TrackGen import LoopingArray
midiGenerator = MidiGenerator(tempo=105)
# Create a looping array of chord C, note C, and notes 64+67
notes = LoopingArray([[60,64,67],[60],[64,67]])
# Create a looping array of beats (note duration, note length)
beats = LoopingArray([(0.5,0.5), (0.25,0.125)])
# Create a looping array of velocities
velocities = LoopingArray([100,90])
midiGenerator.add_track(0, 0, beat=beats, notes=notes, velocities=velocities, length=16)
midiGenerator.write()

Introducing functioniterator

The LoopingArray class can take as a parameter a number of generator classes which modify the way notes are read from the array. For example the following code will read notes from the array by skipping a value.

from midiutil.MidiGenerator import MidiGenerator
from midiutil.TrackGen import LoopingArray, StaticIterator
midiGenerator = MidiGenerator(tempo=105)
notes = LoopingArray([[x] for x in range(60,80)], functioniterator=[('add',StaticIterator(value=2))])
beats = LoopingArray([(0.5,0.5)])
velocities = LoopingArray([100,90])
midiGenerator.add_track(0, 0, beat=beats, notes=notes, velocities=velocities, length=16)
midiGenerator.write()

functioniterator is a list of tuples in the form (function, generator)

function can be: add, dec, mult, div

For example:

notes = LoopingArray(
[[x] for x in range(60,80)],
functioniterator=[
('add',StaticIterator(value=2)),
('dec',StaticIterator(value=1)),
])

Will first add 2 to the index and reduce it by 1. Note that when using StaticIterator, this does not make alot of sense. However when using complex generators such as LoopingArray, then very complex patterns can be achieved.

Complex functioniterator

As already mentioned, one can add a LoopingArray as a functioniterator. In the following example, the note array index is first incremented by 1, then by 2, 1 again, etc…

from midiutil.MidiGenerator import MidiGenerator
from midiutil.TrackGen import LoopingArray, StaticIterator
midiGenerator = MidiGenerator(tempo=105)
notes = LoopingArray([[x] for x in range(60,80)], functioniterator=[('add',LoopingArray([1,2])) ])
beats = LoopingArray([(0.5,0.5)])
velocities = LoopingArray([100,90])
midiGenerator.add_track(0, 0, beat=beats, notes=notes, velocities=velocities, length=16)
midiGenerator.write()

One can build complex patterns such as:

import sys
sys.path.append("../")
from midiutil.TrackGen import LoopingArray
if __name__ == '__main__':
arr = LoopingArray(
[x for x in range(128)],
functioniterator=[
('add', LoopingArray([1, 2, 3], functioniterator=[
('add', LoopingArray([1, 2, 3], functioniterator=[
('add', LoopingArray([1,2,3], id='      array4', debug=True))
],
id='    array3', debug=True)
)
],
id='  array2', debug=True)
)
],
id='array1', debug=True
)
for _ in range(20):
arr.next()

LoopingIndexedArray

A LoopingIndexedArray returns items from an array.

values = [value1, value2, value3, value4, value5]
indexes = [index1, index2, index3, index4, index5]
^
loopindex
returns values[indexes[loopindex]]

on next(), the loopindex is moved. By default the value is incremented by 1, however this can be modified by adding values to the list functioniterator (see below).

Example:

x = LoopingIndexedArray([1,2,3,4,5],[0,1,0,2,0,3])
x.next()
> 1
x.next()
> 2
x.next()
> 1
x.next()
3
x.next()
> 1

LoopingIncrementalIndexedArray

A LoopingIncrementalIndexedArray returns items from an array.

values = [value1, value2, value3, value4, value5]
indexes = [index1, index2, index3, index4, index5]
^
loopindex
noteindex=noteindex+indexes[loopindex]
returns values[noteindex]

on next(), the loopindex is moved. By default the value is incremented by 1, however this can be modified by adding values to the list functioniterator (see below).

Example:

x = LoopingIncrementalIndexedArray([1,2,3,4,5],[0,1,-1,2,0,3])
x.next()
> 1
x.next()
> 1
x.next()
> 2
x.next()
> 1
x.next()
> 3
x.next()
> 3
x.next()
> 1

Note: negative values will move the loopindex to the right

Arpeggiator

So far we have seen adding note loops as tracks. You can also add arpeggios by using the add_arpeggio method.

from midiutil.MidiGenerator import MidiGenerator
from midiutil.Scales import MINOR, buildScale
from midiutil.TrackGen import LoopingIncrementalIndexedArray, LoopingArray
midiGenerator = MidiGenerator(tempo=105)
sc = MINOR
mscale = buildScale(sc, 48, 80)
notes = LoopingArray([
[mscale[x] for x in [0, 2, 4, 6]], # chord 1
[mscale[x] for x in [3, 5, 7, 9]], # chord 2
[mscale[x] for x in [5, 7, 11]] # chord 3
], functioniterator=[('add', LoopingArray([1, 1, 2]))])
chord_beats = LoopingArray([(4, 4), (2, 2)])
notes_beats = LoopingArray([(0.25, 0.25)])
velocities = LoopingArray([120, 120])
note_skip = LoopingArray([0, 1, 2, 3, 1], functioniterator=[('add', LoopingArray([1, 2]))])
midiGenerator.add_arpeggio(0, 0,
chords_beat=chord_beats,
notes_beat=notes_beats,
chords=notes,
velocities=velocities,
note_skip=note_skip,
length=32)
midiGenerator.write()

In this example, the chords inside the LoopingArray notes, are played note by note. The length of the chorded arpeggio is determined by chords_beat. The duration of the notes inside the arpeggio is determined by notes_beat. So in this example, the arpeggio chords are held for 4 beat and then 2 beats. The notes inside the arpeggio are 1/4 in length.

Download from http://code.google.com/p/python-music-gen/

Share

Python implementation of the Goertzel Algorithm – DTMF decoding

Goertzel algorithm: http://en.wikipedia.org/wiki/Goertzel_algorithm

Idea from http://netwerkt.wordpress.com/2011/08/25/goertzel-filter/ and optimized for DTMF decoding.

'''
A python implementation of the Goertzel algorithm to decode DTMF tones.
The wave file is split into bins and each bin is analyzed
for all the DTMF frequencies. The method run() will return a numeric
representation of the DTMF tone.
'''
import wave
import struct
import math
class pygoertzel_dtmf:
    def __init__(self, samplerate):
        self.samplerate = samplerate
        self.goertzel_freq = [1209.0,1336.0,1477.0,1633.0,697.0,770.0,852.0,941.0]
        self.s_prev = {}
        self.s_prev2 = {}
        self.totalpower = {}
        self.N = {}
        self.coeff = {}
        # create goertzel parameters for each frequency so that
        # all the frequencies are analyzed in parallel
        for k in self.goertzel_freq:
            self.s_prev[k] = 0.0
            self.s_prev2[k] = 0.0
            self.totalpower[k] = 0.0
            self.N[k] = 0.0
            normalizedfreq = k / self.samplerate
            self.coeff[k] = 2.0*math.cos(2.0 * math.pi * normalizedfreq)
    def __get_number(self, freqs):
        hi = [1209.0,1336.0,1477.0,1633.0]
        lo = [697.0,770.0,852.0,941.0]
        # get hi freq
        hifreq = 0.0
        hifreq_v = 0.0
        for f in hi:
            if freqs[f]>hifreq_v:
                hifreq_v = freqs[f]
                hifreq = f
        # get lo freq
        lofreq = 0.0
        lofreq_v = 0.0
        for f in lo:
            if freqs[f]>lofreq_v:
                lofreq_v = freqs[f]
                lofreq = f
        if lofreq==697.0:
            if hifreq==1209.0:
                return "1"
            elif hifreq==1336.0:
                return "2"
            elif hifreq==1477.0:
                return "3"
            elif hifreq==1633.0:
                return "A"
        elif lofreq==770.0:
            if hifreq==1209.0:
                return "4"
            elif hifreq==1336.0:
                return "5"
            elif hifreq==1477.0:
                return "6"
            elif hifreq==1633.0:
                return "B"
        elif lofreq==852.0:
            if hifreq==1209.0:
                return "7"
            elif hifreq==1336.0:
                return "8"
            elif hifreq==1477.0:
                return "9"
            elif hifreq==1633.0:
                return "C"
        elif lofreq==941.0:
            if hifreq==1209.0:
                return "*"
            elif hifreq==1336.0:
                return "0"
            elif hifreq==1477.0:
                return "#"
            elif hifreq==1633.0:
                return "D"
    def run(self, sample):
        freqs = {}
        for freq in self.goertzel_freq:
            s = sample + (self.coeff[freq] * self.s_prev[freq]) - self.s_prev2[freq]
            self.s_prev2[freq] = self.s_prev[freq]
            self.s_prev[freq] = s
            self.N[freq]+=1
            power = (self.s_prev2[freq]*self.s_prev2[freq]) + (self.s_prev[freq]*self.s_prev[freq]) - (self.coeff[freq]*self.s_prev[freq]*self.s_prev2[freq])
            self.totalpower[freq]+=sample*sample
            if (self.totalpower[freq] == 0):
                self.totalpower[freq] = 1
            freqs[freq] = power / self.totalpower[freq] / self.N[freq]
        return self.__get_number(freqs)
if __name__ == '__main__':
    # load wav file
    wav = wave.open('dtmf.wav', 'r')
    (nchannels, sampwidth, framerate, nframes, comptype, compname) = wav.getparams()
    frames = wav.readframes(nframes * nchannels)
    # convert wave file to array of integers
    frames = struct.unpack_from("%dH" % nframes * nchannels, frames)
    # if stereo get left/right
    if nchannels == 2:
        left = [frames[i] for i in range(0,len(frames),2)]
        right = [frames[i] for i in range(1,len(frames),2)]
    else:
        left = frames
        right = left
    binsize = 400
    # Split the bin in 4 to average out errors due to noise
    binsize_split = 4
    prevvalue = ""
    prevcounter = 0
    for i in range(0,len(left)-binsize,binsize/binsize_split):
        goertzel = pygoertzel_dtmf(framerate)
        for j in left[i:i+binsize]:
            value = goertzel.run(j)
        if value==prevvalue:
            prevcounter+=1
            if prevcounter==10:
                print value
        else:
            prevcounter=0
            prevvalue=value

Test file with 50ms long DTMF tones separated by 10ms silence:
DTMF test tone

Share

Python plugin development

In order to make programs extendable it is usually a good idea to implement a plugin structure. The following code shows how this can be done in Python.

You can place your plugins in a directory named ‘plugins’ and create an instance of pyplugin by passing the path as the constructor.

p = pyplugin('plugins')

This can be changed to anything you like. Add sub-folders as parameters of the constructor:

p = pyplugin('plugins','plugin-subfolder1','plugins-subfolder2')

Then you can instantiate the plugin by calling:

p.plugin_instance('a.a.cltest')

This will instanciate a class named cltest inside the file plugins\a\a.py

This method is useful if the plugins follow a particular pattern. For example you can have a method run() which is implemented by all the plugins.

import os
class pyplugin:
  def __init__(self, *args):
    self.args = list(args)
    self.plugins = {}
  '''
  Recursively search .py / .pyc files in the plugins directory
  and build a dictionary. This will be used by the plugin_instance
  method to make sure that the plugins is a legitimate one.
  '''
  def load_plugins(self, d=''):
    for f in os.listdir(os.path.join(os.path.sep.join(self.args), d)):
      if os.path.isdir(os.path.join(os.path.abspath(os.path.sep.join(self.args)), d, f)):
        # recursively call plugin directories
        self.load_plugins(os.path.join(d, f))
      elif os.path.isfile(os.path.join(os.path.sep.join(self.args), d, f)):
        # Do not include package initializers
        if f != '__init__.py' and f != '__init__.pyc':
          classname = os.path.join(d, f)
          filename = os.path.join(os.path.sep.join(self.args), d, f)
          # include .py and .pyc files as modules
          if classname[-3:] == '.py' and not self.plugins.has_key(classname[:-3]):
            self.plugins[classname[:-3].replace(os.path.sep, '.')] = filename
          elif classname[-4:] == '.pyc' and not self.plugins.has_key(classname[:-4]):
            self.plugins[classname[:-4].replace(os.path.sep, '.')] = filename
  '''
  Create an instance of the plugin and returns it
  '''
  def plugin_instance(self, plugin=''):
    try:
      path = '.'.join(plugin.split('.')[:-1])
      class_name = plugin.split('.')[-1]
      if self.plugins.has_key(path):
        kls = '.'.join(self.args) + '.' + path + '.' + class_name
        parts = kls.split('.')
        module = ".".join(parts[:-1])
        m = __import__(module)
        for comp in parts[1:]:
          m = getattr(m, comp)
        return m()
      else:
        raise Exception('Plugin %s does not exist' % (plugin))
    except:
      raise Exception('Plugin %s does not exist' % (plugin))
if __name__ == '__main__':
  p = pyplugin('plugins')
  p.load_plugins()
  print p.plugins
  print p.plugin_instance('a.a.cltest')
Share

Accessing class instance variables from decorators

A decorator for a method within a class can access the variables of that class via args[0] inside the wrap(*args,**kw) method.

class mydecorator:
    def __call__(self, f):
        def wrap(*args,**kw):
            print 'calling %s : v1=%s' % (f.__name__, args[0].v1)
        return wrap
class c1():
    def __init__(self, v1):
        self.v1 = v1
    @mydecorator()
    def f1(self):
        pass
m1 = c1(1)
m2 = c1(2)
m1.f1()
m2.f1()
Share

Python decorator to check for a required parameter

Decorators can be used to create a generic return validator. The following code makes sure that if a method has the decorator @checkrequired() and parameter required=True, the return value r is not set to None. This is useful when a check on the return is necessary on several methods.

class checkrequired:
    def __call__(self, f):
        def wrap(*args,**kw):
            r = f(*args)
            if 'required' in kw:
                if kw['required'] == True:
                    if r==None:
                        raise Exception (f.__name__ + " error")
            return r
        return wrap
class myclass:
    @checkrequired()
    def mymethod(self,required=False):
        r = None
        # do code here
        return r

The decorator can also be modified to accept a regular expression so that the return value r is matched against it.

import re
class checkrequired:
    def __init__(self, regex):
        self.regex = regex
    def __call__(self, f):
        def wrap(*args,**kw):
            r = f(*args)
            if 'required' in kw:
                if kw['required'] == True:
                    if r==None:
                        raise Exception (f.__name__ + " not found")
                    else:
                        if re.match(self.regex, r)==None:
                            raise Exception(f.__name__ + ": invalid return value")
            return r
        return wrap
class myclass:
    @checkrequired('^[a-z]+$')
    def mymethod(self,required=False):
        r = None
        r = 'a'
        return r
m = myclass()
m.mymethod(required=True)

line 24: if instead of r=’a’ you have r=’1′ an exception is thrown since it will not match ^[a-z]+$.

Decorators are powerful constructs that can be used to make validation easier to maintain!

Share

HTML5 web workers – fractal generator

Code runs on Chrome although code can be tweaked to run on Safari and Firefox.

The following code paints a fractal on an HTML 5 canvas using a web worker. A web worker mimics a program thread thus allowing the browser to perform heavy operations without affecting the user experience.

Main HTML file

<!DOCTYPE html>
<html>
<head>
<script type="text/javascript" src="jquery-1.6.2.min.js"></script>
<script type="text/javascript">
$(document).ready(function(e) {
var c = document.getElementById('content');
g = c.getContext('2d');
WIDTH = c.width;
HEIGHT = c.height;
g.fillStyle = '#000000';
g.fillRect(0,0,WIDTH,HEIGHT);
var worker = new Worker('fractal.js');
worker.onmessage = function(event) {
g.fillStyle = event.data.col;
g.fillRect(event.data.x,event.data.y,1,1);
}
worker.postMessage();
});
</script>
</head>
<body>
<canvas width="640" height="480" id="content"></canvas>
</body>
</html>

fractal.js

self.onmessage = function(e) {
minx = -2;
maxx = 1;
miny = -1;
maxy = 1;
max_iteration = 50;
for (xp=0;xp<640;xp++) {
for (yp=0;yp<480;yp++) {
x0 = ((maxx-minx)*(xp/640))+minx;
y0 = ((maxy-miny)*(yp/480))+miny;
x = 0;
y = 0;
iteration = 0;
while (((x*x) + (y*y)) < (2*2) && iteration<max_iteration) {
xtemp = (x*x) - (y*y) + x0;
y = (2*x*y) + y0;
x = xtemp;
iteration = iteration + 1;
}
if (iteration==max_iteration) {
self.postMessage({'x':xp,'y':yp,'col':'#000000'});
} else {
self.postMessage({'x':xp,'y':yp,'col':'#'+(iteration*100).toString(16)});
}
}
}
}
Share