Gibber contains a special abstraction for creating mappings between properties of audio, visual and interactive objects 1 2. This abstraction came from thinking about the various modalities that Gibber employs and how they could quickly be combined.
The basic idea is simple: you can create a continuous mapping between any two properties of any two Gibber objects by simply captilizing the property name on the right side of the assignment operator.
mysine = Sine(); mycube = Cube(); mycube.scale = mysine.Frequency mysine.frequency.seq( [220,440,880,1760], 1/4 )
In the above example, since the
Frequency property of our sine oscillator is capitalized during assignment (in the second line of code), a continuous mapping is made between the two elements. This means that as the frequency of our oscillator changes over time, the scale (size) of our cube will also change to reflect the new values.
Behind the scenes, the following actions take place when a this type of continuous mapping is made:
Using metadata included about the properties of each object, the range of values on the righthand property (you could think of this as the output) is mapped to the range of values expected in the lefthand property (or input).
Different forms of sample rate conversion handles the different timescales that the various modalities operate at. For example, if an audio property is mapped to a visual property, an envelope follower is placed on the value of the audio property so that a continuous average is used by the visual object. Conversely, a lowpass filter is placed on the values of visual properties when they are assigned to control audio properties.
Linear -> logarithmic conversions are made as needed to be perceptually 'correct'. For exapmle, audio frequency is perceived logarithmically, while rotation is perceived linearly. Hence, a mapping between freqeuency and rotation needs to accommodate differing perceptual output curves.
The general idea is that Gibberers shouldn't have to concern themselves with these details, and only need to remember to capitalize that righthand property value to have these actions occur.
Gibber's capitalized notation for creating continuous mappings doesn't have to be used between objects of different modalities. You can also easily assign, for example, the pitch of a drum loop to be controlled by the drum loop's output envelope.
mydrums = Drums('x*ox*xo-') mydrums.pitch = mydrums.Out
Note that in the above example, and for audio objects in general, the
Out property is added to objects to enable easy mappings that track the audio output envelope. However, the lowercase version,
out, is not an real property value that can be read or changed.
Out is unique in this fashion.
Although Gibber uses metadata to try and ensure reasonable ranges of values for continuous mappings, these defaults are freely customizable using the
max properties of the mapping objects. After a mapping object is made, it is stored in the capitalized property value for editing.
a = Sine() b = Drums('xoxo') a.frequency = b.Out a.Frequency.min = 200 a.Frequency.max = 300
Mappings can also easily be inverted:
Using Gibber's sequencing techniques, we can easily schedule changes to the
max properties as well as make calls to the invert method.
a.Frequency.min.seq( [100,200,300], ms(500) ) a.Frequency.max.seq( [300,400,500], ms(500) ) a.Frequency.invert.seq( null, ms(1000) )
In the above example, the min and max values are changed every half a second while the mapping is inverted every second.
When continuous mappings are made using audio properties, an envelope follower is placed on the audio property. The size of the envelope used can greatly impact the result of the mapping. For example, small envelopes can cause jerky responses in graphical properties, while longer envelopes can smooth out responses over time.
To adjust the size of an envelope, simply change the
env property of a mapping object.
a = Cube() b = Drums('x*o*x*o-') a.scale = b.Out a.Scale.env = ms(100) // wait a few seconds, and then try a larger envelope a.Scale.env = ms(350)
Gibber allows the
max properties of mapping objects to have their own continous mappings assigned. Below, we create a
Cube and a
Drums object. The output of the drums is continuously mapped to control its pitch. As the cube rotates along its y-axis, its rotation will control the upper bound of the pitch mapping.
drums = Drums('x*ox*xo-') drums.pitch = drums.Out cube = Cube() // continuously rotate on the y-axis cube.mod('rotation.y', .01) drums.Pitch.max = cube.rotation.Y
We can also imagine a situation where the rotation of the cube depends on the output of the drums, and the
max value for the rotation is dependent on the drums pitch. These types of feedback networks fairly easy to create using the mapping abstraction described here.
Although there will be another chapter on using interface elements with this mapping abstraction, for now we'll briefly point out how easy it is to continuously map the position of the mouse cursor in the editor window to control audiovisual objects.
a = Knot() b = Dots() // halftone shader b.scale = Mouse.X // size of dots to x position b.angle = Mouse.Y // angle of dot placement to y
1: It's actually an abstraction of an abstraction... the inspiration was research by Georg Essl in his Urmus project.
2: Many of the concepts in this chapter are discussed in a NIME conference paper I authored .