«  …14 15 16 17 18 19 20 »

More Low Life

2010-12-01 15:55 supercollider

clean-up #16:

Here is another piece of self-referential code. It belongs to my family of low life audiovisual creatures.

SuperCollider document attached below. Note: will only run under older SuperCollider, version <=3.6 with OSX (Cocoa).

At 00:40 I start to type into the document. Mainly hitting the delete key as the system runs.

Attachments:
redEel.scd

hansm-bird

2010-11-30 03:56 supercollider

clean-up #15:

This birdcall synthesis tutorial by Andy Farnell I found very good when it was published. And as I wanted to learn more by synthetic bird songs I ported the Pure Data patches. So here is a version of hansm-bird-control.pd for SuperCollider.

/*
hansm-bird-control.pd
http://obiwannabe.co.uk/tutorials/html/tutorial_birds.html
SC port by redFrik 080430
*/
s.boot;

(
SynthDef(\birdCall, {|
  out= 0, pan= 0, gate= 1, freq= 0, amp= 0.5, atk= 0.5, dcy= 0.5,
  fmod1= 1, atkf1= 0.5, dcyf1= 0.5,
  fmod2= 1, atkf2= 0.5, dcyf2= 0.5,
  amod1= 1, atka1= 0.5, dcya1= 0.5,
  amod2= 1, atka2= 0.5, dcya2= 0.5|
  var env, freq1, freq2, amp1, amp2, fmod, amod, z;
  env= EnvGen.ar(Env([0, amp, 0], [atk, dcy], -4), gate, timeScale:0.9, doneAction:2);
  freq1= EnvGen.ar(Env([0, fmod1, 0], [atkf1, dcyf1], -4), 1, 3000, 0, 0.9);
  freq2= EnvGen.ar(Env([0, fmod2, 0], [atkf2, dcyf2], -4), 1, 3000, 0, 0.9);
  amp1= EnvGen.ar(Env([0, amod1, 0], [atka1, dcya1], -4), 1, 1, 0, 0.9);
  amp2= EnvGen.ar(Env([0, amod2, 0], [atka2, dcya2], -4), 1, 1, 0, 0.9);
  fmod= SinOsc.ar(freq1, 0, amp1, 1);
  amod= 1-SinOsc.ar(freq2, 0, amp2);
  z= SinOsc.ar(freq*7000+300*fmod, 0, amod);
  Out.ar(out, Pan2.ar(z, pan, env));
}).add;
f= {|freq, atk, dcy, fmod1, atkf1, dcyf1, fmod2, atkf2, dcyf2, amod1, atka1, dcya1, amod2, atka2, dcya2|
  Synth(\birdCall, [\freq, freq, \atk, atk, \dcy, dcy,
    \fmod1, fmod1, \atkf1, atkf1, \dcyf1, dcyf1,
    \fmod2, fmod2, \atkf2, atkf2, \dcyf2, dcyf2,
    \amod1, amod1, \atka1, atka1, \dcya1, dcya1,
    \amod2, amod2, \atka2, atka2, \dcya2, dcya2]);
};
)
(
//triple-tailed-tree-troubler
f.value(0.387755, 0.0204082, 0.204082,
  0.367347, 0.571429, 0.734694,
  0.918367, 1, 0.77551,
  0.571429, 0.367347, 0.22449,
  0.0204082, 0.183673, 0.44898);
)
(
//speckled-throated-spew
f.value(0.183673, 0.591837, 0.387755,
  0.0104082, 0.530612, 0.346939,
  0.244898, 0.55102, 0.122449,
  0.387755, 1, 0.612245,
  0.346939, 0.816327, 0.653061);
)
(
//lesser-spotted-grinchwarbler
f.value(0.55102, 0.591837, 0.387755,
  0.0716327, 0.0204082, 0.346939,
  0.0204082, 0.55102, 0.122449,
  0.632653, 1, 0.612245,
  0.346939, 0.816327, 0.653061);
)
(
//long-toed-mudhopper
f.value(0.163265, 0.22449, 0.183673,
  0.00306122, 0.122449, 1,
  0.0612245, 1, 0.77551,
  0.979592, 0.204082, 0.734694,
  1, 0.142857, 0.612245);
)
(
//yellow-yiffled-yaffle
f.value(0.0204082, 0.367347, 0.183673,
  0.0612245, 0, 1,
  0.285714, 0.22449, 0.489796,
  0.367347, 0.387755, 0.734694,
  0.204082, 0.428571, 0.142857);
)
(
//pointy-beaked-beetlefiend
f.value(0.428571, 0.204082, 0.489796,
  0.0204082, 0.795918, 0.591837,
  0.285714, 0.22449, 0.489796,
  0.204082, 0.836735, 0.734694,
  0.77551, 0.428571, 0.142857);
)
(
//african-boojuboolubala
f.value(0.306122, 0.959184, 0.0408163,
  1, 0, 0.591837,
  0.285714, 0.22449, 0.489796,
  0.204082, 0.836735, 0.734694,
  0.77551, 0.428571, 0.142857
);
)
(
//common-muckoink
f.value(0.0204082, 0.8, 0.0816327,
  0.0204082, 0.001, 0.99,
  0.0204082, 0.01, 1,
  1, 0.142857, 0.734694,
  1, 0.0612245, 0.530612);
)

Hamburg

2010-11-29 03:02 supercollider

clean-up #14:

Here is a file from a short introduction to / demonstration of SuperCollider given at the Hochschule für Musik und Theater Hamburg, July 2009. It was all live-coded from scratch during my talk, but in the video above I just run through the lines one by one found in the file (attached).

I recorded this screencast and published the code to serve as an example for people new to and curious of SuperCollider. Pardon the silly music.

The \asdf synth definition is a phase modulation synth with some random panning. \hh and \sn are both built by filtering noise and the \bd is a simple oscillator. All three definitions use a percussive envelope.

All Pdefs (the sequencers sort of) is set to use a quantise of 4. That means that any change I do in the sequencing code waits to kick in until the next bar (4/4, 60 BPM).

At 01:35 I start live-coding some changes to the \bd bass drum. First changing the duration from a static 0.5 to a stream of numbers using the Pseq. Then I change the release time (hard to hear) and last I play with the frequencies. That might give an idea of how one can interact with code in SuperCollider and change the system as it is running.

Updates:

Attachments:
hamburg.scd

Growing Sounds

2010-11-28 03:23 supercollider

clean-up #13:

This is a demonstration of a simple sequencer using the RedGA library - a library for genetic algorithms. See the story /f0blog/work-with-mark-genetics/ for more info and get the classes here: /code/sc/#classes.

The whole program consists of six voices. For each voice, there are individual genomes for the SynthDef as well as for the amplitude pattern (the step-sequencer) and for the envelope shape. The genomes are all randomised at startup or when the 'restart' button is clicked. One can monitor the complete result of one voice (SynthDef+pattern+envelope) with the 'play' button and edit the detailed multi-slider views manually if desired. As the play button is clicked, the SynthDef genome is translated to a phenome (a real working SynthDef) and sent to the server as well as posted to the post window.

With the 'mark' buttons one select which voices that will be parents in the next generation. One can mark from zero up to all six voices, but a more sensible number is two or three. There is the choice of keeping the parent voices alive to the next generation or to overwrite them with new children.

In any case, this program is not meant to be taken completely serious. It is more of a fun toy using a genetic algorithm to create some chaotic and glitchy little sounds.

Below is the printout of the genomes (arrays) and phenomes (SynthDefs) that were produced in the video demonstration.

GENOME:
[ 0.97673571109772, 0.25465285778046, 0.42777442932129, 0.67972803115845, 0.33054959774017, 0.6037335395813, 0.56670892238617, 0.90849816799164, 0.082634687423706, 0.60306036472321, 0.39336848258972, 0.40707266330719, 0.0089198350906372, 0.58077096939087, 0.79928719997406, 0.46543490886688, 0.58306109905243, 0.2658509016037, 0.2371027469635, 0.65999948978424 ]

PHENOME:
(SynthDef('RedGAPhenomeDef_101128_015549', {|out= 0, amp= 1, gate= 1| Out.ar(out, Limiter.ar(LeakDC.ar(VarSaw.ar(SinOsc.kr(Dust.kr(LFSaw.kr(WhiteNoise.kr(WhiteNoise.kr(0.659999).range(0.2658509016037, 0.2371027469635)).exprange(16.005815279484, 9.362154686451)).exprange(8153.3118128777, 198.21830511093)).exprange(1.7444302797318, 12.100901257992)).exprange(12082.596120834, 11342.844269276)).range(-0.14445114135742, 0.35945606231689)*EnvGen.kr(#[ 0, 2, 1, -99, 1, 0.01, 5, -4, 0, 0.02, 5, -4 ], gate, amp, 0, 1, 2))))}).play)

GENOME:
[ 0.20719468593597, 0.044587969779968, 0.080502510070801, 0.57578802108765, 0.082075834274292, 0.75569093227386, 0.49076271057129, 0.017484903335571, 0.86507594585419, 0.24153673648834, 0.65859174728394, 0.0708167552948, 0.86804354190826, 0.01993727684021, 0.6159542798996, 0.10451817512512, 0.55506300926208, 0.35075616836548, 0.57417559623718, 0.94203984737396 ]

PHENOME:
(SynthDef('RedGAPhenomeDef_101128_015552', {|out= 0, amp= 1, gate= 1| Out.ar(out, Limiter.ar(LeakDC.ar(SinOscFB.ar(111.474092, VarSaw.ar(LFNoise2.ar(32.619722, 0.868044, -0.960125).exprange(4845.9039950371, 13178.663110733)).range(0.49076271057129, 0.017484903335571)).range(-0.8389949798584, 0.15157604217529).perform('sqrdif', LFSaw.ar(1055.739533, 1.884080).range(0.11012601852417, -0.29848766326904))*EnvGen.kr(#[ 0, 2, 1, -99, 1, 0.01, 5, -4, 0, 0.02, 5, -4 ], gate, amp, 0, 1, 2))))}).play)

GENOME:
[ 0.093999624252319, 0.045899748802185, 0.94297802448273, 0.28493881225586, 0.53934347629547, 0.98470652103424, 0.042535185813904, 0.16906559467316, 0.80304133892059, 0.043324589729309, 0.060743093490601, 0.010264873504639, 0.48678374290466, 0.13158559799194, 0.052134871482849, 0.19801700115204, 0.09520161151886, 0.15453398227692, 0.5274965763092, 0.16556298732758 ]

PHENOME:
(SynthDef('RedGAPhenomeDef_101128_015556', {|out= 0, amp= 1, gate= 1| Out.ar(out, Limiter.ar(LeakDC.ar(SinOscFB.ar(LFNoise1.kr(0.244920, 0.803041, -0.913351).exprange(19694.436290264, 869.8530125618)).range(0.88595604896545, -0.43012237548828).perform('-', SinOsc.ar(59.563934, 3.770235, 0.288490, -0.063430).range(-0.026432514190674, -0.73682880401611))*EnvGen.kr(#[ 0, 2, 1, -99, 1, 0.01, 5, -4, 0, 0.02, 5, -4 ], gate, amp, 0, 1, 2))))}).play)

GENOME:
[ 0.87658286094666, 0.13238632678986, 0.20194101333618, 0.81867587566376, 0.87399089336395, 0.60985088348389, 0.89901769161224, 0.27304971218109, 0.3851375579834, 0.6437246799469, 0.17010962963104, 0.31229841709137, 0.22293603420258, 0.56832695007324, 0.26486468315125, 0.25054693222046, 0.53271245956421, 0.048996567726135, 0.4028148651123, 0.7767539024353 ]

PHENOME:
(SynthDef('RedGAPhenomeDef_101128_015559', {|out= 0, amp= 1, gate= 1| Out.ar(out, Limiter.ar(LeakDC.ar(LFPar.ar(LFClipNoise.ar(6071.557417, LFCub.kr(15.052203, 1.351127, Vibrato.kr(SyncSaw.kr(6.128215).exprange(1.0750316977501, 8.1160158157349)).range(0.26486468315125, 0.25054693222046)).range(0.6437246799469, 0.17010962963104)).exprange(12204.820652008, 17982.373478413)).range(-0.59611797332764, 0.63735175132751)*EnvGen.kr(#[ 0, 2, 1, -99, 1, 0.01, 5, -4, 0, 0.02, 5, -4 ], gate, amp, 0, 1, 2))))}).play)

GENOME:
[ 0.34140014648438, 0.30175685882568, 0.0056966543197632, 0.80570125579834, 0.04620635509491, 0.73648166656494, 0.52050232887268, 0.045454144477844, 0.40112888813019, 0.80093693733215, 0.16607248783112, 0.6278703212738, 0.67780292034149, 0.21197652816772, 0.92777013778687, 0.5347558259964, 0.61497759819031, 0.93128263950348, 0.67022502422333, 0.17048060894012 ]

PHENOME:
(SynthDef('RedGAPhenomeDef_101128_015602', {|out= 0, amp= 1, gate= 1| Out.ar(out, Limiter.ar(LeakDC.ar(SyncSaw.ar(52.612358, LFTri.ar(LFPulse.kr(PinkNoise.kr(0.927770, 0.069512).exprange(13.588278114796, 4.3183329105377)).exprange(16022.720007896, 3338.1283068657)).exprange(10419.636530876, 928.17380666733)).range(-0.98860669136047, 0.61140251159668).perform('sqrsum', Blip.ar.range(0.34045004844666, -0.65903878211975))*EnvGen.kr(#[ 0, 2, 1, -99, 1, 0.01, 5, -4, 0, 0.02, 5, -4 ], gate, amp, 0, 1, 2))))}).play)

GENOME:
[ 0.21759104728699, 0.19168221950531, 0.96565890312195, 0.2745361328125, 0.71634376049042, 0.31953656673431, 0.075420379638672, 0.73166418075562, 0.21231842041016, 0.14699053764343, 0.48020792007446, 0.18293488025665, 0.93624556064606, 0.65213680267334, 0.49147021770477, 0.69864809513092, 0.49048376083374, 0.61236214637756, 0.0073232650756836, 0.21936917304993 ]

PHENOME:
(SynthDef('RedGAPhenomeDef_101128_015605', {|out= 0, amp= 1, gate= 1| Out.ar(out, Limiter.ar(LeakDC.ar(LFTri.ar(LFPar.ar(LFTri.ar(551.637467, 0.731740, 0.936246, 0.304274).exprange(4262.1220397949, 2956.8709421158)).exprange(6404.3406033516, 1526.8991851807)).range(0.9313178062439, -0.450927734375).perform('ring4', LFDNoise1.ar(21.037774, 0.219369).range(-0.01903247833252, 0.22472429275513))*EnvGen.kr(#[ 0, 2, 1, -99, 1, 0.01, 5, -4, 0, 0.02, 5, -4 ], gate, amp, 0, 1, 2))))}).play)

GENOME:
[ 0.34140014648438, 0.30175685882568, 0.0056966543197632, 0.80570125579834, 0.04620635509491, 0.73648166656494, 0.52050232887268, 0.045454144477844, 0.40112888813019, 0.80093693733215, 0.16607248783112, 0.6278703212738, 0.67780292034149, 0.21197652816772, 0.92777013778687, 0.5347558259964, 0.61497759819031, 0.93128263950348, 0.67022502422333, 0.17048060894012 ]

PHENOME:
(SynthDef('RedGAPhenomeDef_101128_015608', {|out= 0, amp= 1, gate= 1| Out.ar(out, Limiter.ar(LeakDC.ar(SyncSaw.ar(52.612358, LFTri.ar(LFPulse.kr(PinkNoise.kr(0.927770, 0.069512).exprange(13.588278114796, 4.3183329105377)).exprange(16022.720007896, 3338.1283068657)).exprange(10419.636530876, 928.17380666733)).range(-0.98860669136047, 0.61140251159668).perform('sqrsum', Blip.ar.range(0.34045004844666, -0.65903878211975))*EnvGen.kr(#[ 0, 2, 1, -99, 1, 0.01, 5, -4, 0, 0.02, 5, -4 ], gate, amp, 0, 1, 2))))}).play)

GENOME:
[ 0.21759104728699, 0.19168221950531, 0.62269937992096, 0.2745361328125, 0.71634376049042, 0.31953656673431, 0.075420379638672, 0.73166418075562, 0.21231842041016, 0.69631904363632, 0.48020792007446, 0.18293488025665, 0.13190184533596, 0.65213680267334, 0.49147021770477, 0.14417177438736, 0.49048376083374, 0.61236214637756, 0.0073232650756836, 0.21936917304993 ]

PHENOME:
(SynthDef('RedGAPhenomeDef_101128_015625', {|out= 0, amp= 1, gate= 1| Out.ar(out, Limiter.ar(LeakDC.ar(LFTri.ar(LFPar.ar(LFTri.ar(551.637467, 0.731740, 0.131902, 0.304274).exprange(4262.1220397949, 13932.454491854)).exprange(6404.3406033516, 1526.8991851807)).range(0.24539875984192, -0.450927734375).perform('ring4', LFPar.ar(21.037774, 0.438738).range(-0.01903247833252, 0.22472429275513))*EnvGen.kr(#[ 0, 2, 1, -99, 1, 0.01, 5, -4, 0, 0.02, 5, -4 ], gate, amp, 0, 1, 2))))}).play)

Annette

2010-11-27 03:36 supercollider

clean-up #12:

This is a live patch for 4-channel sound written for a friend some time ago. To fully function it expects that you have an external soundcard with 4 inputs (mics) and 4 outputs (speakers) connected. The idea is to be able to route the sound from any of the inputs to any of the outputs and do all kinds of crossfades and mixes in between.

There are two areas with sliders that are used to do the 4x4 routing. One can also record the resulting quad output to disk and play it back, or use previously recorded quad files. There is also a sampler with 28 voices that can be triggered with the computer keyboard (keys A-Z). Additional features include MIDI learn (controllers) plus a preset system that can read and write to disk. Keys '0-9' with and without capslock recall presets while shift-clicking one of the preset buttons in the GUI stores the current setting. With the lag time, one can make all the changes smooth - it interpolates over x seconds.

annette08 screenshot

Attached is the complete code for the program. Sorry for the ugly interface.

It has been tested on SC 3.4 on a OSX with both Cocoa and SwingOSC and should run cross-platform.

Updates:

Attachments:
annette10.scd

2-bit Computer Sonification

2010-11-26 03:57 supercollider

clean-up #11:

As a continuation of the n-bit computer story, here are three snippets of SuperCollider code that sonifies all possible programs of a little 2-bit computer emulator. They all map the sound differently. Note that here I let load and stor instructions increase the program counter one extra step.

Rendered MP3s for corresponding code is attached below.

//2-bit computer
//sonification #1 - all possible programs 0-255 in order, 16 ticks each
(
s.waitForBoot{
  var mem;
  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)};

  var snd;
  SynthDef(\snd, {|amp= 0, freq= 400, width= 0.5, pan= 0|
    var src= LFPulse.ar(freq, 0, width, amp.lag(0, 0.1));
    Out.ar(0, Pan2.ar(FreeVerb.ar(src, 0.2, 0.3), pan));
  }).add;
  s.sync;
  snd= Synth(\snd);

  Routine.run{
    4.do{|m0|
    4.do{|m1|
    4.do{|m2|
    4.do{|m3|
      var op, running= true;
      mem= [m3, m2, m1, m0];  //reordering here makes slightly different pieces
      pc= 0;
      reg= 0;
      "".postln;
      16.do{
        if(running, {
          op= mem[pc];
          snd.set(\amp, 0.7, \freq, op.linexp(0, 3, 120, 1200), \pan, pc/3*2-1, \width, reg/3*0.8+0.1);
          switch(op,
            2r00, {running= false},  //halt
            2r01, {reg= rd.(pc+1); pc= pc+1},  //load  next addy into reg
            2r10, {reg= reg+1%4},  //incr  reg
            2r11, {wr.(rd.(pc+1), reg); pc= pc+1}  //stor  at addy in next byte
          );
          pc= pc+1%mem.size;
          [\pc, pc, \reg, reg, \mem, format.value(mem)].postln;
        });
        0.01.wait;
        snd.set(\amp, 0);
        0.001.wait;
      };
    };};};};
    1.wait;
    snd.free;
  };
};
)
//2-bit computer
//sonification #2 - all possible programs 0-255 in order, maximum 100 ticks
(
s.waitForBoot{
  var mem;
  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)};

  var snd;
  SynthDef(\snd, {|amp= 0, freq= 400, width= 0.5, mod= 0, t_trig= 0, pan= 0|
    var env= EnvGen.ar(Env.perc(0.01, 0.1, 1, 0), t_trig);
    var src= SinOsc.ar(mod, SinOsc.ar(freq, 0, 2pi*width), amp);
    Out.ar(0, Pan2.ar(src*env, pan));
  }).add;
  s.sync;
  snd= Synth(\snd, [\amp, 0.7]);

  Routine.run{
    var cnt= 0;
    4.do{|m0|
    4.do{|m1|
    4.do{|m2|
    4.do{|m3|
      var i= 0, op, running= true;
      mem= [m0, m1, m2, m3];  //reordering here makes slightly different pieces
      pc= 0;
      reg= 0;
      while({running and:{i<100}}, {
        op= mem[pc];
        snd.set(\t_trig, 1, \freq, op.linexp(0, 3, 120, 1200), \pan, pc/3*2-1, \width, pc+1, \mod, pc/3);
        switch(op,
          2r00, {running= false},  //halt
          2r01, {reg= rd.(pc+1); pc= pc+1},  //load  next addy into reg
          2r10, {reg= reg+1%4},  //incr  reg
          2r11, {wr.(rd.(pc+1), reg); pc= pc+1}  //stor  at addy in next byte
        );
        pc= pc+1%mem.size;
        i= i+1;
        0.008.wait;
      });
      ("program:"+cnt).postln;
      cnt= cnt+1;
    };};};};
    1.wait;
    snd.free;
  };
};
)
//2-bit computer
//sonification #3 - all possible programs 0-255 in order, 4 voices with freq from memory
(
s.waitForBoot{
  var mem;
  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)};

  var snds;
  SynthDef(\snd, {|amp= 0, freq= 400, width= 0.5, mod= 0, pm= 0, pan= 0|
    var src= SinOsc.ar(mod, SinOsc.ar(freq, SinOsc.ar(pm, 0, 2pi), 2pi*width), amp);
    Out.ar(0, Pan2.ar(src, pan));
  }).add;
  s.sync;
  snds= {Synth(\snd)}.dup(4);

  Routine.run{
    var cnt= 0;
    4.do{|m0|
    4.do{|m1|
    4.do{|m2|
    4.do{|m3|
      var i= 0, op, running= true;
      mem= [m3, m2, m1, m0];  //reordering here makes slightly different pieces
      pc= 0;
      reg= 0;
      while({running and:{i<16}}, {
        op= mem[pc];
        switch(op,
          2r00, {running= false},  //halt
          2r01, {reg= rd.(pc+1); pc= pc+1},  //load  next addy into reg
          2r10, {reg= reg+1%4},  //incr  reg
          2r11, {wr.(rd.(pc+1), reg); pc= pc+1}  //stor  at addy in next byte
        );
        pc= pc+1%mem.size;
        4.do{|r|
          snds[r].set(
            \amp, running.binaryValue*0.3,
            \freq, mem[r].linexp(0, 3, 300-cnt, 300+cnt),
            \pan, r/3*2-1,
            \width, op+1,
            \mod, reg/2,
            \pm, pc+1
          );
          0.01.wait;
        };
        i= i+1;
      });
      ("program:"+cnt).postln;
      cnt= cnt+1;
    };};};};
    1.wait;
    snds.do{|x| x.free};
  };
};
)

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
*/

«  …14 15 16 17 18 19 20 »