« 18 / 30 »

n-bit Computers

2010-11-24 21:34 supercollider

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. 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 the address 1! (Note: here the trick is first noticed - although the original program said "store at address 0", at 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 disassembler for the emulator above. It will just dump the whole memory (256 bytes) and post which opcode is stored at what address.

(
//--8bit computer disassembler
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 SuperCollider 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

2010-11-24 01:55 supercollider

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 with SC 3.6 Cocoa or earlier I'm afraid. Put it in your startup file 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

2010-11-23 01:27 supercollider

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 SuperCollider 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.

Attachments:


RedBMP

2010-11-22 03:36 supercollider

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.install("redSys");

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.2 MB Windows v3 BMP, 640x480, 72dpi


A Script that Rules the World

2010-11-21 01:26 supercollider

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.

a script that rules the world
//redFrik 2007 - updated 2010
//req. mac OSX, Google Earth, SC 3.6

//--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 google earth started, also disable all overlays in google earth)
(
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.

//--unixCmd version
"osascript -e 'tell application \"Finder\" to activate'".unixCmd;
"osascript -e 'tell application \"TextEdit\" to activate'".unixCmd;
//--NSObject version - this only work on old OSX SuperCollider versions
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.

Attachments:


Eco

2010-11-19 23:15 supercollider

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.

screenrecording of eco system running

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 SuperCollider code is a bit cryptic using a lot of nested arrays for efficiency reasons but near the top, there are user settings to play with.

Here's also a JavaScript version... /software/p5js/eco/, a version for Processing... /software/processing/eco/ and also a C++ version written in Cinder... /software/cinder/eco/ and one in Rust using the nannou framework... /software/nannou/eco/.

Updates:


Whitney Balls

2010-11-19 00:31 supercollider

clean-up #4:

This is something I originally wrote for the article Audiovisuals with SC and now rewrote as separate classes. There are four classes all implementing the same system slightly differently. Two for graphics (Whitney, Whitney2) 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.

whitney balls system running

The code for the above video example is taken from the help file...

(
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;

  Routine({
    var nextTime;
    while({w.isClosed.not}, {
      nextTime= Main.elapsedTime+(1/60);
      u.refresh;
      (nextTime-Main.elapsedTime).max(0.001).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;
)

Updates:

Attachments:


rot

2010-11-17 23:55 supercollider

clean-up #3:

Beat rotation as described here...

music.columbia.edu/~douglas/strange_things/?p=78 (archive.org)

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, "/Users/asdf/soundfiles/amen.wav")  //***edit to match***
b.numChannels  //use instrument \segStereo for 2 channel files and instrument \segMono for 1 channel files
~bpm= 60/(b.duration/16)  //calculate tempo in bpm. 16 is the number of beats in the file. ***edit to match***
c= TempoClock(~bpm/60);  //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,  //***edit to match***
  \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.


« 18 / 30 »