n-bit computers

clean-up #10:

some 3 years ago i was really into writing code in supercollider to emulate/study/sonify various computer systems. here's a short write-up...

a 1-bit computer can only have two states. in the following code such a simple computer is emulated. the two possible opcodes 0 and 1 are set to take some basic actions: 0 will stop the program and 1 will post 'hello'.
there are only a total of four programs possible for this computer. with the line ~mem= [0, 0] we can load the first program [0, 0] into memory. that program means halt, halt and when we run it it will make the emulated computer stop right away. the second program [0, 1] will also stop instantaneously, but program three [1, 0] will post once and then halt. program four [1, 1] in turn will post forever and never halt.

//--all 1bit computer programs
~mem= [0, 0];           //program: halt halt - quits right away
~mem= [0, 1];           //program: halt post - also quits right away
~mem= [1, 0];           //program: post halt - posts hello and then quits
~mem= [1, 1];           //program: post post - posts hello forever

(
//--1bit computer.  load a program into memory before run
var pc= 0;                                                      //program counter
var reg= 0;                                                     //register (unused)
Routine.run{
        var op, running= true;
        while({running}, {
                op= ~mem[pc];
                switch(op,
                        0, {running= false},            //halt
                        1, {"hello".postln}             //post
                );
                pc= pc+1%~mem.size;
                [\pc, pc, \reg, reg, \mem, ~mem].postln;
                0.2.wait;
        });
        ("finished with mem="+~mem).postln;
};
""
)

if we take the same simple principle and implement a 2-bit computer, there are four possible opcodes and 256 different programs we can run (if we just fill the memory with unique combinations and call them programs - not many of these will make sense of course). the actions that the emulator below can take are simple (other actions are of course possible - here just a few standard ones)... halt - stops execution, load will take the next byte in the program and put it into the register, incr will increase the register by one and stor writes the content of the register back into the memory position (address) that is found in the next byte.
most programs will be pointless (at least for the actions implemented here), so i only list five out of 256 programs. so the first program [2r10, 2r10, 2r10, 2r00] will increment the register three times and then stop. program two increments the register, stores that at address 0 and stops. program three will first load 2r10 into the register, then increment the register twice and stop. the fourth program first loads 2r10 into the register, then increment the register, then stores the register at address 0 and last stops.
so far all the programs have been dull and pointless. but not program five! here we start to see the potential as it modifies itself as it runs. program five starts by incrementing the register, then it stores the register at address 3, then again it stores the register but at address 1! (note: here the trick is first noticed - although the original program said store at address 0, in the beginning of the program we overwrote that with address 1), then it loads 2r10 (note: here it wraps around and finds the value at address 0) into the register, then increment the register, then load 2r11 into the register, then store the register at address 1, then load 2r10 into the register, then increase the register, then store the register intro address 3, etc etc. until it finally halts after 16 cycles. because of the self-modifying program code the execution is long and complex to follow. very interesting i find.

//--selected 2bit computer programs
~mem= [2r10, 2r10, 2r10, 2r00]; //program: incr incr incr halt
~mem= [2r10, 2r11, 2r00, 2r00]; //program: incr stor halt halt
~mem= [2r01, 2r10, 2r10, 2r00]; //program: load incr incr halt
~mem= [2r01, 2r10, 2r11, 2r00]; //program: load incr stor halt
~mem= [2r10, 2r11, 2r11, 2r00]; //program: incr stor stor halt

(
//--2bit computer.  load a program into memory before run
var pc= 0;                                                      //program counter
var reg= 0;                                                     //register
var format= {|x| x.collect{|x| x.asBinaryString(2)}};
var rd= {|x| ~mem@@x};
var wr= {|x, y| ~mem.put(x%~mem.size, y)};
Routine.run{
        var op, running= true;
        while({running}, {
                op= ~mem[pc];
                switch(op,
                        2r00, {running= false},         //halt
                        2r01, {reg= rd.(pc+1)},         //load  next addy into reg
                        2r10, {reg= reg+1%4},           //incr  reg
                        2r11, {wr.(rd.(pc+1), reg)}     //stor  at addy in next byte
                );
                pc= pc+1%~mem.size;
                [\pc, pc, \reg, reg, \mem, format.value(~mem)].postln;
                0.2.wait;
        });
        ("finished with mem="+format.value(~mem)).postln;
};
""
)

one variation of the emulator code above (and also for the following 3-4bit computers) would be to let the load and stor instructions also increase the program counter (pc) by 1. so it takes the following byte as an argument and then skips over that for the next cycle. this would be a more common way to implement instructions like load and stor (i do it in the last example - the 8bit computer).

so if a 2-bit computer can start to mutate its code as it runs, imagine what a 3-bit computer is capable of. 16777216 different programs (8.pow(8)) are possible, 8 opcodes and actions and 8 bytes of memory. the actions implemented in the 3-bit emulator below includes and/or operations as well as a jump instruction. all the example programs are pretty lame. e.g. program four makes an endless loop jumping back and forth. one could write a lot more interesting programs.

//--selected 3bit computer programs
~mem= [2r010, 2r011, 2r111, 2r000, 2r010, 2r011, 2r111, 2r000]; //program: incr stor post halt incr stor post halt
~mem= [2r101, 2r111, 2r110, 2r111, 2r011, 2r000, 2r000, 2r000]; //program: and  post or   post stor halt halt halt
~mem= [2r100, 2r101, 2r000, 2r000, 2r000, 2r111, 2r000, 2r000]; //program: jump and  halt halt halt post halt halt
~mem= [2r100, 2r101, 2r000, 2r000, 2r000, 2r100, 2r000, 2r000]; //program: jump and  halt halt halt jump halt halt

(
//--3bit computer.  load a program into memory before run
var pc= 0;                                                              //program counter
var reg= 0;                                                             //register
var format= {|x| x.collect{|x| x.asBinaryString(3)}};
var rd= {|x| ~mem@@x};
var wr= {|x, y| ~mem.put(x%~mem.size, y)};
Routine.run{
        var op, running= true;
        while({running}, {
                op= ~mem[pc];
                switch(op,
                        2r000, {running= false},                //halt
                        2r001, {reg= rd.(pc+1)},                //load  next addy into reg
                        2r010, {reg= reg+1%8},                  //incr  reg
                        2r011, {wr.(rd.(pc+1), reg)},   //stor  at addy in next byte
                        2r100, {pc= rd.(pc+1)-1},               //jump  to addy in reg
                        2r101, {reg= rd.(pc+1)&reg},    //and   reg with next addy
                        2r110, {reg= rd.(pc+1)|reg},    //or    reg with next addy
                        2r111, {reg.postln}                     //post  reg
                );
                pc= pc+1%~mem.size;
                [\pc, pc, \reg, reg, \mem, format.value(~mem)].postln;
                0.2.wait;
        });
        ("finished with mem="+format.value(~mem)).postln;
};
""
)

with a 4-bit computer it starts to get difficult to come up with relevant actions to take for all the 16 opcodes. in the emulator below there is a stack with push and pull opcodes, more math with addition, subtraction and left/right shifts as well as inversion of bits. there is also a random instruction. again the example programs are really dull - it is possible to write much more interesting programs or why not generate programs at random and see which ones make interesting results? like this... ~mem= {16.rand}.dup(16);

//--selected 4bit computer programs
~mem= [2r0010, 2r0010, 2r0011, 2r1111, 2r0000, 2r0000, 2r0000, 2r0000, 2r0000, 2r0000, 2r0000, 2r0000, 2r0000, 2r0000, 2r0000, 2r0000];
~mem= [2r0001, 2r1110, 2r0010, 2r1111, 2r0000, 2r0000, 2r0000, 2r0000, 2r0000, 2r0000, 2r0000, 2r0000, 2r0000, 2r0000, 2r0000, 2r0000];

(
//--4bit computer.  load a program into memory before run
var pc= 0;                                                                              //program counter
var sp= 15;                                                                             //stack pointer
var reg= 0;                                                                             //register
var format= {|x| x.collect{|x| x.asBinaryString(4)}};
var rd= {|x| ~mem@@x};
var wr= {|x, y| ~mem.put(x%~mem.size, y)};
Routine.run{
        var op, running= true;
        while({running}, {
                op= ~mem[pc];
                switch(op,
                        2r0000, {running= false},                               //halt
                        2r0001, {reg= rd.(pc+1)},                               //load  next addy into reg
                        2r0010, {reg= reg+1%16},                                //incr  reg
                        2r0011, {wr.(rd.(pc+1), reg)},                  //stor  at addy in next byte
                        2r0100, {pc= rd.(pc+1)-1},                              //jump  to addy in reg
                        2r0101, {reg= rd.(pc+1)&reg},                   //and   reg with next addy
                        2r0110, {reg= rd.(pc+1)|reg},                   //or    reg with next addy
                        2r0111, {reg.postln},                                   //post  reg
                        2r1000, {wr.(rd.(pc+1), rd.(pc+1).bitXor)},//inv  at addy in next byte
                        2r1001, {wr.(rd.(pc+1), 16.rand)},              //rand  at addy in next byte
                        2r1010, {reg= reg.leftShift(1)},                        //lsr   reg
                        2r1011, {reg= reg.rightShift(1)},               //rsr   reg
                        2r1100, {reg= reg+rd.(pc+1)%16},                        //add   reg val at addy in next byte
                        2r1101, {reg= reg-rd.(pc+1)%16},                        //sub   reg val at addy in next byte
                        2r1110, {wr.(sp, reg); sp= sp-1},               //push  reg to stack
                        2r1111, {reg= rd.(sp+1%16); sp= (sp+1).min(15)}//pull to reg from stack
                );
                pc= pc+1%~mem.size;
                [\pc, pc, \reg, reg, \sp, sp, \mem, format.value(~mem)].postln;
                0.2.wait;
        });
        ("finished with mem="+format.value(~mem)).postln;
};
""
)

things are getting really tricky when implementing an 8-bit computer from scratch. in the example below i only implemented 19 out of the 256 possible opcodes. for the actions/instructions set i took inspiration from the 6502 microprocessor. if you know some assembly you should recognise what the opcodes do. the stack pointer is commented out because none of the 19 active opcodes uses it.

~mem= 0.dup(256); ""            //clear memory

//--selected 8bit computer programs
//ADC, #0x0F, AND, #0x0A, ORA, #0x0F, EOR, #0x10, BRK
 [0x69, 0x0F, 0x29, 0x0A, 0x09, 0x0F, 0x49, 0x0A, 0x00].do{|x, i| ~mem.put(i, x)};

//LDA, #0xFF, STA, #0x20, BRK
 [0xA9, 0xFF, 0x85, 0x20, 0x00].do{|x, i| ~mem.put(i, x)};

//INC,  0x20, NOP,  DEC,  0x21, BRK
 [0xE6, 0x20, 0xEA, 0xC6, 0x21, 0x00].do{|x, i| ~mem.put(i, x)};

//CLC,  BCS, #0x07, SEC,  BCS, #0x07, BRK,  NOP
 [0x18, 0xB0, 0x07, 0x38, 0xB0, 0x07, 0x00, 0xEA].do{|x, i| ~mem.put(i, x)};

(
//--8bit computer.  load a program into memory before run
//implemented some of the 6502 opcodes and some of its flags
var pc= 0x00;                                                                           //program counter
//var sp= 0xFF;                                                                 //stack pointer
var r= 0x00;                                                                            //register
var c= 0x00;                                                                            //carry
var format= {|x| x.collect{|x| x.asHexString(2)}};
var rd= {|x| ~mem@@x};
var wr= {|x, y| ~mem.put(x%~mem.size, y)};
Routine.run{
        var op, running= true;
        while({running}, {
                op= ~mem[pc];
                switch(op,
                        0x65, {r= r+rd.(rd.(pc+1))+c%0x100; pc= pc+1},//ADC aa
                        0x69, {r= r+rd.(pc+1)+c%0x100; pc= pc+1},       //ADC #aa
                        0x29, {r= r&rd.(pc+1); pc= pc+1},               //AND #aa
                        0x25, {r= r&rd.(rd.(pc+1)); pc= pc+1},  //AND aa
                        0x90, {if(c==0, {pc= rd.(pc+1)-1}, {pc= pc+1})},//BCC aa
                        0xB0, {if(c==1, {pc= rd.(pc+1)-1}, {pc= pc+1})},//BCS aa
                        0x00, {running= false},                                 //BRK
                        0x18, {c= 0},                                                   //CLC
                        0xC6, {wr.(rd.(pc+1), rd.(rd.(pc+1))-1%0x100); pc= pc+1},//DEC aa
                        0x49, {r= r.bitXor(rd.(pc+1)); pc= pc+1},       //EOR #aa
                        0x45, {r= r.bitXor(rd.(rd.(pc+1))); pc= pc+1},//EOR aa
                        0xE6, {wr.(rd.(pc+1), rd.(rd.(pc+1))+1%0x100); pc= pc+1},//INC aa
                        0xA9, {r= rd.(pc+1); pc= pc+1},                 //LDA #aa
                        0xA5, {r= rd.(rd.(pc+1)); pc= pc+1},            //LDA aa
                        0xEA, {'nop'.postln},                                   //NOP
                        0x09, {r= r|rd.(pc+1); pc= pc+1},               //ORA #aa
                        0x05, {r= r|rd.(rd.(pc+1)); pc= pc+1},  //ORA aa
                        0x38, {c= 1},                                                   //SEC
                        0x85, {wr.(rd.(pc+1), r); pc= pc+1},            //STA aa
                        {("opcode not implemented"+op).warn}
                );
                pc= pc+1%~mem.size;
                [\pc, pc.asHexString(2), \r, r.asHexString(2), \c, c, \op, op.asHexString(2)].postln;
                0.2.wait;
        });
        ("finished with mem="+format.value(~mem)).postln;
};
""
)

and here's a dissassembler for the emulator above. it will just dump the whole memory (256 bytes) and post which opcode is stored at what address.

(
//--8bit computer dissassembler
var res= [], adr= [0];
var putImmediate= {|op, val| res= res++[op+"#"++val.asHexString(2)]};
var putAbsolute= {|op, val| res= res++[op+"$"++val.asHexString(2)]};
var putOpcode= {|op| res= res++[op]};
var i= 0;

while({i<~mem.size}, {
        switch(~mem[i],
                0x65, {putAbsolute.("ADC", ~mem[i+1]); i= i+2},
                0x69, {putImmediate.("ADC", ~mem[i+1]); i= i+2},
                0x29, {putImmediate.("AND", ~mem[i+1]); i= i+2},
                0x25, {putAbsolute.("AND", ~mem[i+1]); i= i+2},
                0x90, {putAbsolute.("BCC", ~mem[i+1]); i= i+2},
                0xB0, {putAbsolute.("BCS", ~mem[i+1]); i= i+2},
                0x00, {putOpcode.("BRK"); i= i+1},
                0x18, {putOpcode.("CLC"); i= i+1},
                0xC6, {putAbsolute.("DEC", ~mem[i+1]); i= i+2},
                0x49, {putImmediate.("EOR", ~mem[i+1]); i= i+2},
                0x45, {putAbsolute.("EOR", ~mem[i+1]); i= i+2},
                0xE6, {putAbsolute.("INC", ~mem[i+1]); i= i+2},
                0xA9, {putImmediate.("LDA", ~mem[i+1]); i= i+2},
                0xA5, {putAbsolute.("LDA", ~mem[i+1]); i= i+2},
                0xEA, {putOpcode.("NOP"); i= i+1},
                0x09, {putImmediate.("ORA", ~mem[i+1]); i= i+2},
                0x05, {putAbsolute.("ORA", ~mem[i+1]); i= i+2},
                0x38, {putOpcode.("SEC"); i= i+1},
                0x85, {putAbsolute.("STA", ~mem[i+1]); i= i+2},
                {("opcode not implemented"+~mem[i]).warn; i= i+1}
        );
        adr= adr.add(i);
});
"adr: op:\n-----------------".postln;
res.do{|x, i| (" $"++adr[i].asHexString(2)+x).postln};
""
)

i did write a full 6502 emulation that is in a working state. it is written as a class and takes up about 2000 lines of sc code. i will clean it up and publish it another day. i also did the CPU65C02 version that sits in the AppleIIc computer and the 6510 that is in the commodore64.

speak errors&warnings

clean-up #9:

here's a little tool originally made for visually impaired supercollider users. press the 'esc' key and the built-in computer voice will read the last error message posted. hold down 'alt' and press 'esc' and it will read the last warning message posted. if there are no errors or warnings it won't say anything.
this will only work on osx i'm afraid. put it in your startup.rtf document to make it to load by default.

//redFrik 2007
//find last posted error or warning and speak it using the built in computer voice (osx only).
//'esc'     - to speak last error
//'alt+esc' - to speak last warning
(
Document.globalKeyDownAction_{|doc, key, mod, code|
        var d, i;
        if(code==27, {                          //esc
                d= Document.listener.string;
                if(mod&524288==524288, {        //esc+alt
                        i= d.findBackwards("WARNING:");
                        if(i.notNil, {
                                ("warning"+(d.copyRange(i, i+200).split($\r)[1])).speak;
                        });
                }, {                                            //plain esc
                        i= d.findBackwards("ERROR:");
                        if(i.notNil, {
                                d.copyRange(i, i+100).split($\r)[0].speak;
                        });
                });
                ""
        });
}
)

/*
//--test
OOO.new
1.asd
1\2
11
s.quit
{SinOsc.ar}.play        //warns about localhost not running
*/

newscoolish

clean-up #8:

this is a port of a pd patch by martin brinkmann that in turn is a port of a reaktor ensemble by lazyfish. i wrote this sc patch in 2006 and recently revisited the code, added a preset system and made it run under the current supercollider version 3.4.
though there are a lot of things that could be improved. the graphical interface is particularly rough and ugly.

attached is the code and a short mp3 with example sounds.


AttachmentSize
Package icon newscoolish1+.zip13.13 KB

RedBMP

clean-up #7:

today i cleaned up and finished a class belonging to my redSys quark. the class can read and write bitmap images (.bmp files). to get it run the following command in supercollider...

Quarks.checkoutAll;

and then install redSys via the gui...

Quarks.gui;

it's a fairly slow and useless class but well, it can generate images relatively fast. and saving to disk is also quite quick. but avoid drawing the images to screen. the built in makeWindow method is very slow as it goes through width*height number of pixels and draws a rectangle for each using Pen.

(
b= RedBMP(640, 480, 32);
b.data= {|i|
        var x= i%b.width;
        Color.red(sin(x/b.width*2pi+sin(x*0.11)), sin(i*0.01+sin(x*0.1)).abs);
}.dup(b.width*b.height);
b.write("~/Desktop/redbmp.bmp");
)

this code outputs the following file (notice the alpha channel)...
1.2mb windows v3 bmp, 640x480, 72dpi

a script that rules the world

clean-up #6:

this is a hack from 2007. it was done in cocoacollider, but now i updated it using the standard SCNSObject in supercollider. it works by talking to google earth via applescript.

//redFrik 2007 - updated 2010
//req. mac osx, google earth

//--init
(
s.waitForBoot{
        SynthDef(\redGoogleKick, {|out= 0, t_trig= 1, freq= 50, dur= 0.5, envFreq= 500, envAtk= 0.005, envRel= 0.1, amp= 1|
                var z, env, fenv, click;
                env= EnvGen.ar(Env.linen(0, 0, dur, amp), t_trig);
                fenv= EnvGen.kr(Env([envFreq, envFreq-freq*0.275+freq, freq], [envAtk, envRel]), t_trig);
                click= EnvGen.ar(Env.linen(0, 0, 0), t_trig);
                z= SinOsc.ar(fenv, 0, env, click);
                Out.ar(out, z);
        }).add;
        SynthDef(\redGoogleSynth, {|out= 0, t_trig= 1, freq= 200, atk= 0.005, dec= 0.5, pm= 0.5, amp= 1|
                var z, env, pmod;
                env= EnvGen.kr(Env.perc(atk, dec, 1, -3), t_trig);
                pmod= LFSaw.ar(freq*2.pow(TIRand.kr(1, 3, t_trig)), 0, env*pm);
                z= SinOsc.ar(freq, pmod, env);
                Out.ar(out, z*amp);
        }).add;
        SynthDef(\redGoogleDelay, {|in= 0, out= 0, depth= 3, time= 0.45, amp= 1, dist= 2, pan= 0|
                var src, z;
                src= In.ar(in, 1);
                z= CombL.ar(src, 1.5, time.min(1.5), depth, amp, src);
                Out.ar(out, Pan2.ar(Clip.ar(z*dist, -1, 1), pan));
        }, #[\ir, \ir, 0, 0.5, 0, 0, 0]).add;
};
f= {|cmd|
        {
                var o;
                o= SCNSObject("NSAppleScript", "initWithSource:", ["tell application \"Google Earth\"\n"++cmd++"\nend tell"]);
                o.invoke("executeAndReturnError:", [nil], true);
                o.release;
        }.defer;
};
f.value("open");
)

//--run (wait until localhost and googlearth started, also disable all overlays in googleearth)
(
f.value("SetViewInfo{latitude:0, longitude:0, distance:10000000, tilt:0, azimuth:0} speed 5");
d= Synth(\redGoogleDelay, [\out, 0, \in, 32]);
k= Synth(\redGoogleKick, [\t_trig, 0]);
e= Synth(\redGoogleSynth, [\out, 32, \t_trig, 0]);
Tdef(\redGoogle).play;
Tdef(\redGoogle, {
        var dist, xxx= 0, yyy= 0;
        300.do{|i|
                if(i%25==0, {
                        if(i%100==0, {
                                k.set(\t_trig, 1, \amp, 0.1, \out, 32, \freq, 2000, \dur, 1.5, \envAtk, 0.005, \envRel, 0.2);
                        });
                        e.set(\t_trig, 1, \freq, [500, 600, 700].wrapAt(i), \amp, 0.5, \dec, 0.5, \pm, 0.2);
                        if(i%4==0, {
                                xxx= xxx+1.0.rand2;
                                yyy= yyy+20.rand2;
                                f.value("SetViewInfo{latitude:"++1.0.rand2++", longitude:"++yyy++", distance:10000000, tilt:0, azimuth:0} speed 0.4");
                        });
                });
                (1/25).wait;
        };
        f.value("SetViewInfo{latitude:0, longitude:0, distance:10000000, tilt:0, azimuth:0} speed 0.5");
        300.do{|i|
                if(i%25==0, {
                        dist= [400, 600, 700, 800].wrapAt(i);
                        k.set(\t_trig, 1, \amp, 1, \out, 32, \freq, 50, \dur, 0.5, \envAtk, 0.005, \envRel, 0.1);
                        e.set(\t_trig, 1, \freq, dist, \amp, 0.5, \dec, 0.5, \pm, 1-(i/300));
                        f.value("SetViewInfo{latitude:0, longitude:0, distance:"++8000000++", tilt:"++45.rand++", azimuth:0} speed 5");
                }, {
                        if(i%25==1, {
                                f.value("SetViewInfo{latitude:0, longitude:"++([100, -100].choose)++", distance:"++5000000++", tilt:0, azimuth:10} speed 0.5");
                        });
                });
                (1/25).wait;
        };
        d.set(\dist, 4);
        f.value("SetViewInfo{latitude:52.45, longitude:-1.93, distance:300, tilt:0, azimuth:0} speed 0.5");
        50.do{|i|
                if(i%25==0, {
                        k.set(\t_trig, 1, \amp, 1, \out, 32, \freq, 50, \dur, 0.5, \envAtk, 0.005, \envRel, 0.1);
                        e.set(\t_trig, 1, \freq, [100, 500, 600, 700].wrapAt(i), \amp, 0.5, \dec, 1.1, \pm, 2);
                });
                (1/25).wait;
        };
        d.set(\dist, 5);
        500.do{|i|
                if(i%25==0, {
                        k.set(\t_trig, 1, \amp, 1, \out, 32, \freq, 50, \dur, 0.5, \envAtk, 0.005, \envRel, 0.1);
                        e.set(\t_trig, 1, \freq, [200, 500, 600, 700].wrapAt(i), \amp, 0.5, \dec, 1.1, \pm, 2);
                        f.value("SetViewInfo{latitude:52.45, longitude:-1.93, distance:"++200.rrand(300)++", tilt:"++(i%45)++", azimuth:"++(i%180)++"} speed 1");
                }, {
                        if(i%20==18, {
                                k.set(\t_trig, 1, \amp, 0.3, \out, [0, 1].choose, \freq, [2100, 100].wrapAt(i), \dur, 0.9, \envAtk, 0.1.linrand, \envRel, i%3);
                        })
                });
                (1/25).wait;
        };
})
)

f.value("quit");
/*
SCNSObject.dumpPool;
SCNSObject.freePool;
*/

//--some notes:

there are two ways to run applescripts from within supercollider. one is to construct a string with osascript -e and a unixCmd, the other is creating a cocoa NSAppleScript object with the SCNSObject cocoa binding class. the later method is slightly more involved but runs way faster.

the following two examples do the same thing - switches to Finder.

//--unixCmd version
"osascript -e 'tell application \"Finder\" to activate'".unixCmd
//--NSObject version
a= SCNSObject("NSAppleScript", "initWithSource:", ["tell application \"Finder\" to activate"]);
a.invoke("executeAndReturnError:", [nil], true);
a.release;

SCNSObject.dumpPool;    //should be 0
SCNSObject.freePool;     //free stray objects if any left

also i've attached a simple class that wraps the functionality. it's called RedEarth.

AttachmentSize
Package icon RedEarth.zip2.97 KB

eco

clean-up #5:

an ecosystem as described in Gary William Flake's book - "The Computational Beauty of Nature" (page 191). i started coding it 1,5 years ago but never finished it until today.

white = empty space
green = plant
red = herbivore
blue = carnivore.


* For every time step:
    * For every empty cell, e:
        * If e has three or more neighbors that are plants, then e will become a plant at the next time step (assuming it isn't trampled by a herbivore or carnivore).
    * For every herbivore, h (in random order):
        * Decrease energy reserves of h by a fixed amount.
        * If h has no more energy, then h dies and becomes an empty space.
        * Else, if there is a plant next to h, then h moves on top of the plant, eats it, and gains the plant's energy.
            * If h has sufficient energy reserves, then it will spawn a baby herbivore on the space that it just exited.
        * Else, h will move into a randomly selected empty space, if one exists, that is next to h's current location.
    * For every carnivore, c (in random order):
        * Decrease energy reserves of c by a fixed amount.
        * If c has no more energy, then c dies and becomes an empty space.
        * Else, if there is a herbivore next to c, then c moves on top of the herbivore, eats it, and gains the herbivore's energy.
            * If c has sufficient energy reserves, then it will spawn a baby carnivore on the space that it just exited.
        * Else, c will move into a randomly selected empty space that is next to c's current location. If there are no empty spaces, then c will move through plants.

the rules are fairly simple but the result is complex. carnivores feed on herbivores that in turn feed on plants. populations grow, get overpopulated and die out just like in nature. the sc code is a bit cryptic using a lot nested arrays for efficiency reasons but near the top there are user settings to play with.

here's also a version for processing...
eco

and a mac osx application written in cinder...
eco.app.zip (228k)
ecoApp.cpp (12k)

AttachmentSize
File eco.rtf9.48 KB

whitney balls

clean-up #4:

this is something i originally wrote for the article Audiovisuals with SC and now rewrote as separate classes. there are three classes all implementing the same system slightly differently. one for graphics (Whitney) and the other two for patterns (Pwhitney, Pwhitney2).
the principle for this system is described by John Whitney in his book Digital Harmony (1980) and reimplemented musically by Jim Bumgardner in his Whitney Music Box (2006). the idea is simple but complex patterns arise - both in graphics and in harmony/rhythm. the innermost ball moves at a certain speed, the ball next to it doubles that speed and the ball next to that triples the speed etc. there's a sound played for each ball as it passes through 0. each ball's sound plays at a different frequency mapped to some scale, overtone series or something else.

update 131118: added Whitney2 class and new help docs - just download the attached zip file again

the code for the above video example is taken from the helpfile...

(
s.latency= 0.05;
s.waitForBoot{
        var width= 500, height= 500;
        var w= Window("Whitney balls", Rect(99, 99, width, height), false);
        var u= UserView(w, Rect(0, 0, width, height));
       
        SynthDef(\sin, {|freq= 400, amp= 0.2, pan= 0, rel= 0.9|
                var e= EnvGen.ar(Env.perc(0.005, rel), 1, doneAction:2);
                var z= SinOsc.ar(freq, e*pi, e);
                Out.ar(0, Pan2.ar(z, pan, amp));
        }).send(s);
        s.sync;
       
        //--set up the whitney system
        f= Whitney(20, {|ball, i|
                Synth(\sin, [\freq, (64+i).midicps, \amp, 2/f.num]);
                Pen.fillOval(Rect.aboutPoint(Point(ball.x, ball.y), 5, 5));
        });
       
        u.drawFunc= {
                Pen.rotate(2pi*0.75, width*0.5, height*0.5);
                Pen.translate(width*0.5, height*0.5);
                Pen.strokeColor= Color.red;
                Pen.line(Point(0, 0), Point(0, width*0.5));
                Pen.stroke;
                Pen.color= Color.grey(1, 0.7);
               
                f.next;                         //update the system
                f.balls.do{|b, i|               //draw the balls
                        Pen.strokeOval(Rect.aboutPoint(Point(b.x, b.y), 4, 4));
                };
        };
       
        CmdPeriod.doOnce({if(w.isClosed.not, {w.close})});
        u.background= Color.black;
        w.front;
       
        u.animate= true;        //replace this line with the one below if you use swingosc
        //Routine({while({w.isClosed.not}, {u.refresh; (1/60).wait})}).play(AppClock);
};
)

f.speed= 0.02                   //change speed
f.speed= 0.002
f.theta= 0                      //restart
f.spread= 15                    //change distance between balls
f.spread= 5
(
f.func= {|ball, i|
        Synth(\sin, [
                \freq, i.linexp(0, f.num-1, 300, 3000),
                \amp, i.linlin(0, f.num-1, 0.1, 0.05),
                \rel, i.linlin(0, f.num-1, 1, 0.2),
                \pan, 0.4.rand2
        ]);
        Pen.fillOval(Rect.aboutPoint(Point(ball.x, ball.y), 5, 5));
}
)
f.num= 10
f.num= 30
f.num= 55
f.speed= 0.01
f.speed= 0.0002
f.speed= 0.001
f.speed= 0.005

(
f.func= {|ball, i|
        Synth(\sin, [
                \freq, (i.degreeToKey(Scale.indian)+48).midicps,
                \amp, i.linlin(0, f.num-1, 0.1, 0.05),
                \rel, i.linlin(0, f.num-1, 2, 0.2),
                \pan, 0.4.rand2
        ]);
        Pen.fillOval(Rect.aboutPoint(Point(ball.x, ball.y), 6, 6));
};
f.num= 36;
f.theta= 0;
f.spread= 6;
f.speed= 0.001;
)

AttachmentSize
Package icon Whitney.zip50.29 KB

rot

clean-up #3:

beat rotation as described here...
http://music.columbia.edu/~douglas/strange_things/?p=78
implemented as a method for SequenceableCollection. some test code...

s.boot
(
SynthDef(\segMono, {|out= 0, buffer, amp= 0.5, rate= 1, offset= 0, atk= 0.01, rel= 0.1, gate= 1|
        var env= EnvGen.ar(Env.asr(atk, amp, rel), gate, doneAction:2);
        var src= PlayBuf.ar(1, buffer, rate*BufRateScale.ir(buffer), 1, offset*BufFrames.ir(buffer));
        OffsetOut.ar(out, Pan2.ar(src*env));
}).add;
SynthDef(\segStereo, {|out= 0, buffer, amp= 0.5, rate= 1, offset= 0, atk= 0.01, rel= 0.1, gate= 1|
        var env= EnvGen.ar(Env.asr(atk, amp, rel), gate, doneAction:2);
        var src= PlayBuf.ar(2, buffer, rate*BufRateScale.ir(buffer), 1, offset*BufFrames.ir(buffer));
        OffsetOut.ar(out, src*env);
}).add;
)

b= Buffer.read(s, "sounds/amen.wav")
b.numChannels           //use instrument \segStereo for 2 channel files and instrument \segMono for 1 channel files

60/(b.duration/16)      //calculate tempo in bpm. 16 is the number of beats in the file

c= TempoClock(1/(b.duration/16));       //create a tempo clock in the tempo of the file
Pdef(\test).play(c, quant:1)
(       //rot example 1 - shuffle
Pdef(\test, Pbind(
        \instrument, \segStereo,
        \buffer, b,
        \dur, 1,
        \amp, 0.75,
        \offset, Pseq((0..15).rot(2, 1, 0, 4)/16, inf)
))
)
(       //rot example 2 - double tempo
Pdef(\test, Pbind(
        \instrument, \segStereo,
        \buffer, b,
        \dur, 0.5,
        \amp, 0.75,
        \offset, Pseq((0..15).rot(2, 1, 1, 4)/16, inf)
))
)
(       //rot example 3 - double and triple tempo
Pdef(\test, Pbind(
        \instrument, \segStereo,
        \buffer, b,
        \dur, Pseq([0.5, 0.25, 0.25], inf),
        \amp, 0.75,
        \offset, Pseq((0..15).rot(0, 1, 1, 8)/16, inf)
))
)
(       //rot example 4 - subdivide into 32 segments
Pdef(\test, Pbind(
        \instrument, \segStereo,
        \buffer, b,
        \dur, Pseq([0.5, 0.25, 0.25], inf),
        \amp, 0.75,
        \offset, Pseq((0..31).rot(1, 1, 1, 4)/32, inf)
))
)
(       //rot example 5 - back to original
Pdef(\test, Pbind(
        \instrument, \segStereo,
        \buffer, b,
        \dur, 1,
        \amp, 0.75,
        \offset, Pseq((0..15).rot(0, 0, 0, 4)/16, inf)
))
)
Pdef(\test).stop
b.free;

in the mp3 recording of the above code, each rotation example gets to play for 16 beats.


AttachmentSize
Package icon extSequenceableCollection-rot.sc_.zip924 bytes

Pages

Subscribe to f0blog RSS