// John Legelis
// Section D
// FINAL
// Board dimensons
var b
var boardW = 200
var boardH = 350
var boardX = 10
var boardY = 10
// Oscillator dimensions/variables
var oscSpacing = boardH/4.1
var numberboxS = 20
// Dial dimensions/variables
var dR = 17
var dialStroke = 4
var dialSpacing = boardW/5
var theta = Math.PI/2
var overDial = false
var moving = false
var iy
var oldTheta
var thetaMin = -1/4*Math.PI
var thetaMax = 5/4*Math.PI
var pressed = false
var oscOn = false
var c = 2**(1/12) // Frequency diffrence between half steps
var cf = 2 **(1/100/12) // Frequency diffrence between cents
var volKnob = 0 // Index of volume knob
var pitchKnob = 1 // Index of pitch knob
var fineKnob = 2 // Index of fine knob
var wList = ["z", "x", "c", "v", "b", "n", "m", ",", ".", "/" ]
var bList = ["s", "d", false, "g", "h", "j", false, "l", ";"]
function setup() {
createCanvas(600, 385);
background(0);
noFill()
// Draw the board
b = new ThreeWaveBoard(boardX, boardY, boardW, boardH)
b.drawBoard()
}
function ThreeWaveBoard(X,Y,W,H) {
// Board Dimensions
this.bX = X
this.bY = Y
this.bW = W
this.bH = H
this.bStroke = 4
this.oscillators = []
// Draw board border ==> rectangle
this.drawBoard = function() {
stroke(255)
strokeWeight(this.bStroke)
rect(this.bX, this.bY, this.bW, this.bH)
strokeWeight(2)
rect(this.bW/2,this.bY + 50, 10,10)
strokeWeight(1)
textSize(15)
text("FAT", this.bW/2 + 15, this.bY+60)
textSize(29)
text("3 Wave Oscillator", this.bX+8, this.bY+40)
// Make 3 oscillators
for (osc=0; osc<3; osc++){
var oX = this.bX
var oY = this.bY + oscSpacing*(osc+1)
var oW = this.bW
var oNum = osc + 1
this.oscillators.push(new oscillator(oX, oY, oW, oNum))
this.oscillators[osc].drawoscillator()
}
}
// Update board
this.updateBoard = function() {
stroke(255)
strokeWeight(this.bStroke)
rect(this.bX, this.bY, this.bW, this.bH)
strokeWeight(1)
textSize(26)
text("3 Wave Oscillator", this.bX+8, this.bY+40)
// Update 3 oscillators
for (osc=0; osc<3; osc++){
this.oscillators[osc].updateOscillators()
}
}
}
function oscillator(X,Y,W,num) {
// Oscillator Dimensions
this.oX = X
this.oY = Y
this.oW = W
this.oStroke = 4
this.oTextStroke = 1
this.oTextSize = 15
this.number = num
this.dials = []
this.wave = new p5.Oscillator()
this.f = false
this.fine = 0
this.volume = 0
// Draw oscillator ==> line, number box, number
this.drawoscillator = function() {
//line and number box
stroke(255)
strokeWeight(this.oStroke)
line(this.oX, this.oY, this.oX+this.oW, this.oY)
rect(this.oX, this.oY, 20, 20)
// Number text
strokeWeight(this.oTextStroke)
textSize(this.oTextSize)
text(num, this.oX+5, this.oY+15)
// Make volume dial
var dX = this.oX + dR + this.oW/5 * (2)
var dY = this.oY + oscSpacing/2
this.dials.push(new Dial(dX, dY, dR, theta, -12, 12, "Volume"))
this.dials[0].drawDials()
// Make pitch dial
var dX = this.oX + dR + this.oW/5 * (3)
var dY = this.oY + oscSpacing/2
this.dials.push(new Dial(dX, dY, dR, theta, -24, 24, " Pitch"))
this.dials[1].drawDials()
// Make fine dial
var dX = this.oX + dR + this.oW/5 * (4)
var dY = this.oY + oscSpacing/2
this.dials.push(new Dial(dX, dY, dR, theta, -12, 12, " Fine"))
this.dials[2].drawDials()
}
this.updateOscillators = function() {
// Line and number box
stroke(255)
strokeWeight(this.oStroke)
line(this.oX, this.oY, this.oX+this.oW, this.oY)
rect(this.oX, this.oY, 20, 20)
// Number text
strokeWeight(this.oTextStroke)
textSize(this.oTextSize)
text(num, this.oX+5, this.oY+15)
for (i=0; i<this.dials.length; i++){
this.dials[i].drawDials()
}
}
}
function Dial(X, Y, R, theta, min, max, kind) {
// Dial dimensions
this.dialX = X
this.dialY = Y
this.dialR = R
this.stroke = 4
this.l1X = X
this.l1Y = Y
this.l2X = X + R*cos(theta)
this.l2Y = Y - R*sin(theta)
this.theta = theta
this.over = false
this.kind = kind
// Ranges of each dials
this.min = min
this.max = max
this.val = 0 // value of dial initialized to 0
// Turns a decimal radian angle into a number rounded to a specific fration of the circle related to the ticks of the dial
this.inc = function(dec) {
this.range = (this.max - this.min)/2
var increment = 3/4*Math.PI/(this.range)
var d = dec/increment
var r = round(d)
var num = r * increment
return num // Returns radian value along tick marker
}
// Draw dial shape and text
this.drawDials = function() {
this.l2X = X + R*cos(this.theta)
this.l2Y = Y - R*sin(this.theta)
strokeWeight(this.stroke)
stroke(255)
ellipse(this.dialX, this.dialY, this.dialR * 2, this.dialR * 2) // Dial circle
line(this.l1X, this.l1Y, this.l2X, this.l2Y) // Dial line
strokeWeight(1)
text(this.val, this.dialX - 4, this.dialY + 32)
text(this.kind, this.dialX - 25, this.dialY - 22)
stroke(0)
}
}
function draw() {
stroke(255)
noFill()
background(0)
b.updateBoard()
drawPiano(230,20)
// If a key is being pressed freq is its frequnncy
if (keyIsPressed & !oscOn && typeof whichKey() == "string" && whichKey() == key) {
oscOn = true
for(waves = 0; waves < b.oscillators.length; waves++) {
// Math for adjusting pitch and volume according to knobs
b.oscillators[waves].f = frequencyfunc(whichKey()) * (c ** (b.oscillators[waves].dials[pitchKnob].val)) * (cf ** (b.oscillators[waves].dials[fineKnob].val))
b.oscillators[waves].volume = 0.5 + map(b.oscillators[waves].dials[volKnob].val, -12, 12, -0.5 ,0.5)
// Update oscilator parameters
b.oscillators[waves].wave.amp(b.oscillators[waves].volume)
b.oscillators[waves].wave.freq(b.oscillators[waves].f)
b.oscillators[waves].wave.start()
}
}
if (oscOn){
// If another key is pressed while one is already down, priortize newest key
if (whichKey != key){
for(waves = 0; waves < b.oscillators.length; waves++) {
b.oscillators[waves].f = frequencyfunc(whichKey()) * (c ** (b.oscillators[waves].dials[pitchKnob].val)) * (cf ** (b.oscillators[waves].dials[fineKnob].val))
b.oscillators[waves].wave.freq(b.oscillators[waves].f)
b.oscillators[waves].volume = 0.5 + map(b.oscillators[waves].dials[volKnob].val, -12, 12, -0.5 ,0.5)
b.oscillators[waves].wave.amp(b.oscillators[waves].volume)
}
}
if (!keyIsPressed){
oscOn = false
}
}
// Turn sounds off if oscillator is off --> no key is pressed
if (!oscOn) {
for(waves = 0; waves < b.oscillators.length; waves++) {
b.oscillators[waves].wave.stop()
}
}
// Check each dial in each oscillator
for(osc=0; osc < b.oscillators.length; osc++){
for(d=0; d < b.oscillators[osc].dials.length; d++){
// If clicked / dragged on dial adjust the angle of specific dial respectively
if ((dist(mouseX, mouseY, b.oscillators[osc].dials[d].dialX, b.oscillators[osc].dials[d].dialY)) <= (dR) & (mouseIsPressed) && !moving) {
b.oscillators[osc].dials[d].over = true
moving = true
iy = mouseY
oldTheta = b.oscillators[osc].dials[d].theta
}
if (!mouseIsPressed) {
b.oscillators[osc].dials[d].over = false
moving = false
}
if (b.oscillators[osc].dials[d].over == true) {
var rawTheta = (oldTheta - map((iy - mouseY)*3, 0, height, 0, 2*Math.PI)) // Smooth theta from mouse
var cTheta = constrain(rawTheta, thetaMin, thetaMax) // Constrained theta based on min and max
var iTheta = b.oscillators[osc].dials[d].inc(cTheta) // Theta chopped to increment
b.oscillators[osc].dials[d].val = round((map(iTheta, thetaMin, thetaMax, b.oscillators[osc].dials[d].max, b.oscillators[osc].dials[d].min)))
b.oscillators[osc].dials[d].theta = iTheta
}
}
}
}
// Return string value of a keyboard key, otherwise false
function whichKey(){
if ( keyIsDown(90) || keyIsDown(83) || keyIsDown(88) || keyIsDown(68) || keyIsDown(67) || keyIsDown(86) || keyIsDown(71) || keyIsDown(66) || keyIsDown(72) || keyIsDown(78) || keyIsDown(74) || keyIsDown(77) || keyIsDown(188)|| keyIsDown(76) || keyIsDown(190)|| keyIsDown(186)|| keyIsDown(191) ) {
k = key
}
if ((keyIsPressed) && (key == k)) {
return str(k)
}
else{
return false
}
}
// Function that draws the piano shapes and text
function drawPiano(x,y){
var whiteW = 35
var whiteH = 200
var blackW = 20
var blackH = 115
// White Keys
for (i=0; i<10; i++){
push()
translate(x,y)
fill(255)
rect(i*whiteW, 0, whiteW, whiteH)
fill(0)
textSize(25)
text(wList[i],i*whiteW+ whiteW/2 - 6, whiteH - 30)
pop()
}
// Black Keys
for (j=0; j<9; j++){
if ((j != 2) & (j != 6)) { // Exclude black key between E and F, and B and C
push()
translate(x,y)
fill(0)
rect((j+1) * whiteW - blackW/2, 0, blackW, blackH)
fill(255)
textSize(22)
text(bList[j],(j+1) * whiteW - blackW/2 + 6 , blackH - 15)
pop()
}
}
fill(255)
stroke(255)
textSize(30)
text("Click and Drag Dials!", 250,290)
text("Play piano with keyboard!", 250, 340)
noFill()
strokeWeight(5)
stroke(255)
rect(240,250,340,110)
}
// Return the frequency of a keyboard key given string of key pressed
function frequencyfunc(str) {
if (str == "z") {
f = 523.25 // C5
} else if (str == "s") {
f = 554.37 // C#5
} else if (str == "x") {
f = 587.33 // D5
} else if (str == "d") {
f = 622.25 // Eb5
} else if (str == "c") {
f = 659.25 // E5
} else if (str == "v") {
f = 698.46 // F5
} else if (str == "g") {
f = 739.99 // F#5
} else if (str == "b") {
f = 783.99 // G5
} else if (str == "h") {
f = 830.61 // Ab5
} else if (str == "n") {
f = 880.00 // A5
} else if (str == "j") {
f = 932.33 // Bb5
} else if (str == "m") {
f = 987.77 // B5
} else if (str == ",") {
f = 1046.50 // C6
} else if (str == "l") {
f = 1108.73 // C#6
} else if (str == ".") {
f = 1174.66 // D6
} else if (str == ";") {
f = 1244.51 // Eb6
} else if (str == "/") {
f = 1318.51 // E6
}
else {
f = false
}
return f
}
Click within the project to start!
My final project took a lot of effort on the back end side especially on the dial interaction. In order to make them clickable and draggable and easy to interface with, a lot of thinking and debugging had to be done in order for them to behave properly. Then the p5.Oscillators were used as a basis of the sound of my Oscillator. That package took some troubleshooting but eventually I worked out how to create this monophonic synthesizer. I am very proud of this instrument as it is highly functional and can be used as a real live instrument. It was lots of fun to work on and see it come together.
]]>The goal for my final project is to make an oscillator/synthesizer with a record and playback function. The user interface will resemble the following sketch.
The components of this software are a keyboard as well as a drum machine. I will aim to automatically quantize the rhythms in order to give the playback a better sound and buff out difficulty with keyboard delay etc.
The keyboard functionality will be linked to actual keys on the computer of the user and the pitch bend function will be actuated by the mouse. The drum pad will be actuated in a similar way to the keyboard with the number keys. I am a bit apprehensive about all the levels of interaction the user is intended to have in the end in terms of clickable buttons etc. I hope this product will be fully functional as a fun musical instrument.
]]>The MiniMoog and the Casio VL-1 are two synthesizers from the 1970’s. The MiniMoog is an analog synthesizer while the Casio VL-1 is a digital synthesizer. They are both some of the first of their respective kinds. The MiniMoog was a instrument that became an instant success and was used by well known performers of every genre and defined the sound of an era. The VL-1 on the other hand was developed 9 years after the MiniMoog. By this time, synthesizers were less novel and were ready to enter the consumer market and Casio decided to develop a product to fill this market. The VL-1, unlike the MiniMoog, was digital and therefore was not competitive with the MiniMoog. Instead the VL-1 synthesizer took advantage of developments in computing with digital synthesis that allowed miniaturization and portability. Some argue that the Casio product is moreso a toy rather than a practical instrument.
The MiniMoog (left) and the Casio VL-1 (right)
For my final project I am aiming to emulate something of the nature of these instruments, I think it is likely that I will end up creating something closer to the toy-like LV-1 rather than the professional revolutionary MiniMoog
]]>// John Legelis
// Section D
var t // turtle
var yF = 273// Winning Y value
var w // State of play (Play, Win, or Lose)
var s // osilator incriment
//load background image
function preload() {
maze = loadImage("https://i.imgur.com/OPGsUjg.png")
}
function setup() {
createCanvas(400, 300);
background(0);
image(maze,0,0)
fill(0,255,0)
noStroke()
rectMode(CORNER)
rect(361, yF, 33,10)
// initialize t
t = makeTurtle(20,0)
t.setWeight(4)
mouseX = 20
mouseY = 1
s = 0
w = 0
yF = 263
}
function draw() {
//Oscilate between colors at winning and loosing screens
s = s + 1
if (s % 3 == 1){
var lcol = [255,255,255]
var wcol = [0,255,0]
}
else {
var lcol = [255,0,0]
var wcol = [255,255,255]
}
// retrive brightness at turtle location
var p = maze.get(t.x, t.y)
var Tbright = brightness(p)
// If in "play" mode, move turtle towards the mouse
if (w == 0) {
theta = t.angleTo(mouseX, mouseY)
t.right(theta)
t.forward(1)
}
//if hit wall, lost
if (Tbright < 100){
w = -1
}
// if below "win line", won
if (t.y > yF) {
w = 1
}
// if lost...
if (w == -1){
background(lcol)
stroke(255)
textSize(50)
text("G A M E O V E R", 10, 150)
textSize(25)
text("mouse in box to restart", 40, 30)
noStroke()
fill(0)
rectMode(CENTER)
rect(25,25, 10,10)
}
//if won...
if (w == 1){
background(wcol)
stroke(255)
textSize(50)
text("! Y O U W O N !", 10, 150)
stroke(255)
textSize(25)
text("mouse in box to restart", 40, 30)
noStroke()
fill(0)
rectMode(CENTER)
rect(25,25, 10,10)
}
//If lost or won and mouse is in box initialize and go back to "play" mode
if (((w==-1) || w==1) & (mouseX > 25 - 10) && (mouseX < 25 + 10) &&
(mouseY > 25 - 10) && (mouseY < 25 + 10)) {
w = 0
setup()
}
if (mouseIsPressed){
w = 1
}
}
function turtleLeft(d) {
this.angle -= d;
}
function turtleRight(d) {
this.angle += d;
}
function turtleForward(p) {
var rad = radians(this.angle);
var newx = this.x + cos(rad) * p;
var newy = this.y + sin(rad) * p;
this.goto(newx, newy);
}
function turtleBack(p) {
this.forward(-p);
}
function turtlePenDown() {
this.penIsDown = true;
}
function turtlePenUp() {
this.penIsDown = false;
}
function turtleGoTo(x, y) {
if (this.penIsDown) {
stroke(this.color);
strokeWeight(this.weight);
line(this.x, this.y, x, y);
}
this.x = x;
this.y = y;
}
function turtleDistTo(x, y) {
return sqrt(sq(this.x - x) + sq(this.y - y));
}
function turtleAngleTo(x, y) {
var absAngle = degrees(atan2(y - this.y, x - this.x));
var angle = ((absAngle - this.angle) + 360) % 360.0;
return angle;
}
function turtleTurnToward(x, y, d) {
var angle = this.angleTo(x, y);
if (angle < 180) {
this.angle += d;
} else {
this.angle -= d;
}
}
function turtleSetColor(c) {
this.color = c;
}
function turtleSetWeight(w) {
this.weight = w;
}
function turtleFace(angle) {
this.angle = angle;
}
function makeTurtle(tx, ty) {
var turtle = {x: tx, y: ty,
angle: 0.0,
penIsDown: true,
color: color(128),
weight: 1,
left: turtleLeft, right: turtleRight,
forward: turtleForward, back: turtleBack,
penDown: turtlePenDown, penUp: turtlePenUp,
goto: turtleGoTo, angleto: turtleAngleTo,
turnToward: turtleTurnToward,
distanceTo: turtleDistTo, angleTo: turtleAngleTo,
setColor: turtleSetColor, setWeight: turtleSetWeight,
face: turtleFace};
return turtle;
}
When approaching this project I had an idea instantly to use the turtle to make some sort of maze game. Given our recent experience in using brightness and get image I was able to analyze a maze image and detect the walls very efficiently. I polished the game with a win and lose screen and a quick restart function based on mouse position.
]]>Teenage Engineering is a young company from Stockholm Sweden. They developed the OP-1 instrument in response to the creativity-stifling environment of a completely unstructured Digital Audio Workspace.
The OP-1 as a digital instrument is far more restrictive to the user that creating music on a computer would be. The creators claim that “…[the] limits boost the creativity. Limitations are OP-1’s biggest feature.”
The device draws inspiration from retro 80’s synthesizers such as the Casio VL-1, which some developers grew up with and discovered that the physical barriers of older syths spurred unconventional ideas and creativity.
Sample of OP-1 in use.
There are 11 separate synthesizer chips in the device that use varying methods to create different types of sounds.
]]>Boats and Waves
// John Legelis
// Section D
var boat = []
function setup() {
createCanvas(480, 480);
background(0);
}
function draw() {
background(135, 206, 250)
//Land terrain
var land = 0.003;
var landS = 0.00005;
stroke("green");
beginShape();
for (i = 0; i < width; i++) {
var h = (millis() * landS) + (i * land);
var y = map(noise(h), 0, 1, 100, 0);
line(i, y, i, height);
}
endShape();
// Rear wave
var back = 0.0051;
var backS = 0.0002;
stroke(0,142,170);
beginShape();
for (i = 0; i < width; i++) {
var h = (millis() * backS) + (i * back);
var y = map(noise(h), 0, 1, 200, 0);
line(i, y, i, height);
}
endShape();
//probablity that determines if boat is drawn
if (random() < 0.03){
boat.push(makeBoat())
}
//Draw and move the boat if made
for (var h = 0; h < boat.length; h++){
boat[h].draw()
boat[h].move()
}
//middle wave terrain
var mid = 0.0054;
var midS = 0.0004;
stroke(0,153,183);
beginShape();
for (j = 0; j < width; j++) {
var h = (millis() * midS) + (j * mid);
var y = map(noise(h), 0, 1, 300, 200);
line(j, y, j, height);
}
endShape();
//foreground wave
var fore = 0.006;
var foreS = 0.0007;
stroke(0,172,206);
beginShape();
for (j = 0; j < width; j++) {
var h = (millis() * foreS) + (j * fore);
var y = map(noise(h), 0, 1, height, 300);
line(j, y, j, height);
}
endShape();
}
//object containig boat
function makeBoat(){
var bo = {x:480, y: 230, move: moveBoat, draw: drawBoat}
return bo
}
//what boat looks like
function drawBoat(){
var randsailH = random(30,50)
noStroke()
fill("brown")
rect(this.x, this.y, 60,20)
fill("white")
triangle(this.x + 10, this.y, this.x + 35, this.y - randsailH, this.x + 25, this.y)
triangle(this.x + 40, this.y, this.x + 65, this.y - randsailH, this.x + 55, this.y)
}
// Move that boat
function moveBoat(){
this.x = this.x - 4
}
This week’s sketch was a rather large undertaking as it involved using objects on our own for the first time. It took quite some time to be able to properly implement an object without having my hand held through the lab but I eventually succeeded. I stumbled upon the random sail movement by accident but I loved the effect of billowing sails in the wind.
Recently I visited the Carnegie Museum of Art down the road for the first time since before this summer. There have been several new installations since I last visited the museum but one stuck out specifically which was an exhibit by the Guerrilla Girls. The Guerrilla Girls are a self defined as a group of “feminist activist artists”. The aim to target discrepancies in the art world between representation of women’s works vs men’s. Among the groups tactics for bring attention to these sexist discrepancies are absurdism and borderline threatening. One of their favorite facts to demonstrate the underrepresentation of female artist is as follows:
The group consists of an ever changing flux of people who remain anonymous in order to keep the attention on the issues instead of the members. I admire this group because of their frankness, aggression, and successful unorthodox methods.
]]>// John Legelis
// Section D
var imgH = 480
var pR = 10
var colorList = []
var cWidth = 360
var cHeight = 480
var sourceimg = "https://i.imgur.com/PHP8Htm.jpg"
var pimg = "https://i.imgur.com/R1D8XFS.jpg"
var Ploaded
var Sloaded;
function preload() {
Sloaded = loadImage(sourceimg)
Ploaded = loadImage(pimg)
}
function setup() {
createCanvas(360, 480);
background(0);
Sloaded.loadPixels()
}
function draw() {
for (x=0; x < cWidth/pR; x++){
colorList[x] = []
for (y=0; y < cHeight/pR; y++){
colorList[x][y] = Sloaded.get(x*pR, y*pR)
noStroke(0)
tint(colorList[x][y])
image(Ploaded, x*pR, y*pR, pR, pR)
}
}
noLoop()
}
This week’s project seemed at first a bit intimidating but after thinking about it, I decided creating a mosaic representation of a portrait using “tiles” of another image would be an approachable yet interesting approach. The portrait photo this project is based on is a photo I took of my friend in a car holding her small dog named Rocky. The tile photo I used for the mosaic was a close up of Rocky’s face.
While browsing previous week’s looking outwards responses, I stumbled upon Jessica’s article from Week 7 on data visualizations. She chose to write about a New York Times Graph that displays how different demographics spend their time throughout the day.
I agree that the creator’s artistic touch was manifested in the composition and intrinsic complexity of the data being represented. Though the graph doesn’t look extraordinarily eye catching, the data it represents is fascinating and the way it has been organized makes it intuitive to understand. It is difficult to have the results displayed in a data visualization obvious at first glance. As more facets are added to the data, the more information and variables must be squished into one visual, and things can get messy. The creator of this visualization elegantly combined a huge amount of data in a way that can be understood immediately.
]]>Professor Meejin Yoon is the head of the architecture department at the Massachusetts Institute of Technology and she is the first woman to hold this post. She was born in Seoul, Korea and lived and studied in the United States. She has degrees from Cornell and Harvard in urban architecture.
Yoon’s work has often revolved around lighting and the impacts of light pollution on the environment. I am personally supportive of these types of projects because I believe that as society has become more nocturnal, we need to be conscious of our impacts on the environment in non-obvious ways such as light pollution. Her particular work on projects in boston (my hometown) are very inspiring due to their benefits seen to the public space.
In her presentation Yoon rarely if ever had slides with text on them. Instead when she was using her slideshow to accompany her talk, she showed visuals that went along with what she was talking about. This caused the listener to not only pay attention to what Yoon was saying, but also created attention grabbing slides that kept the listener engaged. This method of presenting caused Yoon to be a key part of the presentation instead of simply a reader for the slides.
]]>