Midterm- Sankofa: Patterns of the Past

Concept and Artistic Vision

The concept of Sankofa, derived from the Akan people of Ghana, embodies the idea that one should remember the past to foster positive progress in the future.   The Akan tribe, part of the larger Ashanti (or Asante) group, encapsulates a rich cultural heritage that emphasizes the importance of history and self-identity. The word “Sankofa” translates to “to retrieve,” embodying the proverb, “Se wo were fi na wosankofa a yenkyi,” meaning “it is not taboo to go back and get what you forgot.” This principle highlights that understanding our history is crucial for personal growth and cultural awareness.

This philosophy has greatly inspired my project. Adinkra symbols, with their deep historical roots and intricate patterns, serve as a central element of my work. These symbols carry meanings that far surpass my personal experiences, urging me to look back at my heritage. I aim to recreate these age-old symbols in a modern, interactive format that pays homage to their origins. It’s my way of going back into the past to get what is good and  moving forward with it.

 

Embedded Sketch

Images

 Coding Translation and Logic

The core of my sketch is a dynamic grid-based visualization that reflects Adinkra symbols, infused with movement and interaction through music. Here’s how I approached this creative endeavor:

Creating a Grid Layout

I divided the canvas into a grid structure, with each cell serving as a small canvas for a unique Adinkra symbol. I utilized a 2D array to manage the placement of these symbols efficiently.

    • I defined variables for columns and rows to control the grid structure.
    • I calculated cellSize for evenly spaced symbols.
let x = currentCol * cellSize;

let y = currentRow * cellSize;
Pattern Assignment

I created an array of Adinkra patterns, randomly assigning them to each grid cell for a vibrant, ever-evolving display.

    • I looped through the grid and calling drawPattern() to render each symbol.
function initializePatterns() {
  patterns = [
    drawThickVerticalLines,
    drawNestedTriangles,
    drawSymbols,
    drawZebraPrint,
    drawDiamondsInDiamond,
    drawCurves,
    drawThickHorizontalLines,
    drawSquareSpiral,
    drawSpiralTriangles,
    thinLines,
    verticalLines,
    drawXWithDots,
  ];
}

let colorfulPalette = [
  "#fcf3cf", // Light cream
  "#DAF7A6", // Light green
  "#FFC300", // Bright yellow
  "#FF5733", // Bright red
  "#C70039", // Dark red
  "#900C3F", // Dark magenta
];


function initializeColors() {
  colors = [
    color(255, 132, 0), // Vibrant Orange
    color(230, 115, 0), // Darker Orange
    color(191, 87, 0), // Earthy Brownish Orange
    color(140, 70, 20), // Dark Brown
    color(87, 53, 19), // Rich Brown
    color(255, 183, 77), // Light Golden Orange
  ];
}



function drawSpiralTriangles(x, y, size) {
  strokeWeight(2);
  // Check the mode to set the stroke accordingly
  if (currentMode === 0) {
    // Regular mode: Use random colors from the regular palette
    stroke(random(colors));
  } else if (currentMode === 1) {
    // Colorful mode: Use colors from the colorfulPalette
    stroke(random(colorfulPalette));
  } else if (currentMode === 2) {
    // Random Size Mode: Use random colors from the regular palette
    stroke(random(colors));
  }
  noFill();

  // Adjust the initial size to ensure the triangle fits inside the cell
  let adjustedSize = size * 0.9; // Reduce size slightly for padding

  // Draw the recursive triangles centered in the cell
  recursiveTriangle(
    x - adjustedSize / 2,
    y - adjustedSize / 2,
    adjustedSize,
    5
  );
}



function recursiveTriangle(x, y, size, depth) {
  if (depth == 0) return;

  // Draw the outer triangle
  let half = size / 2;
  triangle(x, y, x + size, y, x + half, y + size);

  // Recursively draw smaller triangles inside
  recursiveTriangle(x, y, size / 2, depth - 1); // Top-left
  recursiveTriangle(x + half / 2, y + size / 2, size / 2, depth - 1); // Center
  recursiveTriangle(x + half, y, size / 2, depth - 1); // Top-right
}



function drawZigZagPattern(x, y, size) {
  if (currentMode === 0) {
    // Regular mode: Use random colors from the regular palette
    stroke(random(colors));
  } else if (currentMode === 1) {
    // Colorful mode: Use colors from the colorfulPalette
    stroke(random(colorfulPalette));
  } else if (currentMode === 2) {
    // Random Size Mode: Use random colors from the regular palette
    stroke(random(colors));
  }
  noFill();

  let amplitude = size / 4;
  let frequency = size / 5;

  // Draw zigzag shape and add dots
  beginShape();
  for (let i = 0; i <= size; i += frequency) {
    let yOffset = (i / frequency) % 2 == 0 ? -amplitude : amplitude; // Create zigzag pattern
    let currentX = x - size / 2 + i; // Current X position
    let currentY = y + yOffset; // Current Y position
    vertex(currentX, currentY);

    // Calculate the vertices of the triangle
    if (i > 0) {
      // The triangle's vertices are:
      // Previous vertex
      let previousY = y + ((i / frequency) % 2 == 0 ? amplitude : -amplitude);
      let triangleVertices = [
        createVector(currentX, currentY), // Current peak
        createVector(currentX - frequency / 2, previousY), // Left point
        createVector(currentX + frequency / 2, previousY), // Right point
      ];

      // Calculate the centroid of the triangle
      let centroidX =
        (triangleVertices[0].x +
          triangleVertices[1].x +
          triangleVertices[2].x) /
        3;
      let centroidY =
        (triangleVertices[0].y +
          triangleVertices[1].y +
          triangleVertices[2].y) /
        3;

      // Draw a dot at the centroid
      strokeWeight(5); // Set stroke weight for dots
      point(centroidX, centroidY); // Draw the dot
    }
  }
  endShape();
}



function drawXWithDots(x, y, size) {
  noFill();

  if (currentMode === 0) {
    // Regular mode: Use random colors from the regular palette
    stroke(random(colors));
  } else if (currentMode === 1) {
    // Colorful mode: Use colors from the colorfulPalette
    stroke(random(colorfulPalette));
  } else if (currentMode === 2) {
    // Random Size Mode: Use random colors from the regular palette
    stroke(random(colors));
  }

  // Draw the two diagonal lines to form the "X"
  line(x - size / 2, y - size / 2, x + size / 2, y + size / 2); // Line from top-left to bottom-right
  line(x - size / 2, y + size / 2, x + size / 2, y - size / 2); // Line from bottom-left to top-right

  // Set fill for the dots
  if (currentMode === 0) {
    // Regular mode: Use random colors from the regular palette
    stroke(random(colors));
  } else if (currentMode === 1) {
    // Colorful mode: Use colors from the colorfulPalette
    stroke(random(colorfulPalette));
  } else if (currentMode === 2) {
    // Random Size Mode: Use random colors from the regular palette
    stroke(random(colors));
  }
  let dotSize = 10; // Size of the dots

  // Calculate positions for the dots in each triangle formed by the "X"
  // Top-left triangle
  ellipse(x - size / 4, y - size / 4, dotSize, dotSize);

  // Top-right triangle
  ellipse(x + size / 4, y - size / 4, dotSize, dotSize);

  // Bottom-left triangle
  ellipse(x - size / 4, y + size / 4, dotSize, dotSize);

  // Bottom-right triangle
  ellipse(x + size / 4, y + size / 4, dotSize, dotSize);
}



//thin lines
function verticalLines(x, y, size) {
  if (currentMode === 0) {
    // Regular mode: Use random colors from the regular palette
    stroke(random(colors));
  } else if (currentMode === 1) {
    // Colorful mode: Use colors from the colorfulPalette
    stroke(random(colorfulPalette));
  } else if (currentMode === 2) {
    // Random Size Mode: Use random colors from the regular palette
    stroke(random(colors));
  }
  strokeWeight(2);
  let gap = size / 5;
  for (let i = 0; i < 6; i++) {
    line(-size / 2 + gap * i, -size / 2, -size / 2 + gap * i, size / 2);
  }
}



// Thick Vertical Lines
function drawThickVerticalLines(x, y, size) {
  if (currentMode === 0) {
    // Regular mode: Use random colors from the regular palette
    stroke(random(colors));
  } else if (currentMode === 1) {
    // Colorful mode: Use colors from the colorfulPalette
    stroke(random(colorfulPalette));
  } else if (currentMode === 2) {
    // Random Size Mode: Use random colors from the regular palette
    stroke(random(colors));
  }
  strokeWeight(10); // Thick line weight
  let gap = size / 5; // 5 lines with gaps
  for (let i = 0; i < 6; i++) {
    line(-size / 2 + gap * i, -size / 2, -size / 2 + gap * i, size / 2);
  }
}



// Thick Horizontal Lines
function drawThickHorizontalLines(x, y, size) {
  if (currentMode === 0) {
    // Regular mode: Use random colors from the regular palette
    stroke(random(colors));
  } else if (currentMode === 1) {
    // Colorful mode: Use colors from the colorfulPalette
    stroke(random(colorfulPalette));
  } else if (currentMode === 2) {
    // Random Size Mode: Use random colors from the regular palette
    stroke(random(colors));
  }
  strokeWeight(10); // Thick line weight
  let gap = size / 6; // 5 lines with gaps
  for (let i = 0; i < 6; i++) {
    line(
      -size / 2,
      -size / 2 + gap * (i + 1),
      size / 2,
      -size / 2 + gap * (i + 1)
    );
  }
}



// Thin Horizontal Lines
function thinLines(x, y, size) {
  if (currentMode === 0) {
    // Regular mode: Use random colors from the regular palette
    stroke(random(colors));
  } else if (currentMode === 1) {
    // Colorful mode: Use colors from the colorfulPalette
    stroke(random(colorfulPalette));
  } else if (currentMode === 2) {
    // Random Size Mode: Use random colors from the regular palette
    stroke(random(colors));
  }
  strokeWeight(2); // Thick line weight
  let gap = size / 6; // 5 lines with gaps
  for (let i = 0; i < 6; i++) {
    line(
      -size / 2,
      -size / 2 + gap * (i + 1),
      size / 2,
      -size / 2 + gap * (i + 1)
    );
  }
}



// Nested Triangles
function drawNestedTriangles(x, y, size) {
  let triangleSize = size;
  noFill();
  if (currentMode === 0) {
    // Regular mode: Use random colors from the regular palette
    stroke(random(colors));
  } else if (currentMode === 1) {
    // Colorful mode: Use colors from the colorfulPalette
    stroke(random(colorfulPalette));
  } else if (currentMode === 2) {
    // Random Size Mode: Use random colors from the regular palette
    stroke(random(colors));
  }
  strokeWeight(2);
  for (let i = 0; i < 4; i++) {
    triangle(
      -triangleSize / 2,
      triangleSize / 2,
      triangleSize / 2,
      triangleSize / 2,
      0,
      -triangleSize / 2
    );
    triangleSize *= 0.7;
  }
}



// West African Symbols/Geometric Shapes
function drawSymbols(x, y, size) {
  noFill();
  let symbolSize = size * 0.6;
  strokeWeight(2);
  if (currentMode === 0) {
    // Regular mode: Use random colors from the regular palette
    stroke(random(colors));
  } else if (currentMode === 1) {
    // Colorful mode: Use colors from the colorfulPalette
    stroke(random(colorfulPalette));
  } else if (currentMode === 2) {
    // Random Size Mode: Use random colors from the regular palette
    stroke(random(colors));
  }

  // Circle with horizontal/vertical line cross
  ellipse(0, 0, symbolSize, symbolSize);
  line(-symbolSize / 2, 0, symbolSize / 2, 0);
  line(0, -symbolSize / 2, 0, symbolSize / 2);

  // Small triangles within
  for (let i = 0; i < 3; i++) {
    let triSize = symbolSize * (0.3 - i * 0.1);
    triangle(
      0,
      -triSize / 2,
      triSize / 2,
      triSize / 2,
      -triSize / 2,
      triSize / 2
    );
  }
}



// Zebra Print
function drawZebraPrint(x, y, size) {
  if (currentMode === 0) {
    // Regular mode: Use random colors from the regular palette
    stroke(random(colors));
  } else if (currentMode === 1) {
    // Colorful mode: Use colors from the colorfulPalette
    stroke(random(colorfulPalette));
  } else if (currentMode === 2) {
    // Random Size Mode: Use random colors from the regular palette
    stroke(random(colors));
  }
  strokeWeight(2);
  let stripes = 10;
  for (let i = 0; i < stripes; i++) {
    let step = i * (size / stripes);
    line(-size / 2 + step, -size / 2, size / 2 - step, size / 2);
    line(size / 2 - step, -size / 2, -size / 2 + step, size / 2);
  }
}



function drawSquareSpiral(x, y, size) {
  if (currentMode === 0) {
    // Regular mode: Use random colors from the regular palette
    stroke(random(colors));
  } else if (currentMode === 1) {
    // Colorful mode: Use colors from the colorfulPalette
    stroke(random(colorfulPalette));
  } else if (currentMode === 2) {
    // Random Size Mode: Use random colors from the regular palette
    stroke(random(colors));
  }
  strokeWeight(4); // Set the stroke weight for the spiral
  noFill(); // No fill for the square spiral

  let step = size / 10; // Define the step size for each movement inward
  let currentSize = size; // Start with the full square size

  let startX = -currentSize / 2; // Initial X position (top-left corner)
  let startY = -currentSize / 2; // Initial Y position (top-left corner)

  beginShape(); // Start drawing the shape

  // Draw the spiral by progressively making the square smaller and moving inward
  while (currentSize > step) {
    // Top edge
    vertex(startX, startY);
    vertex(startX + currentSize, startY);

    // Right edge
    vertex(startX + currentSize, startY + currentSize);

    // Bottom edge
    vertex(startX, startY + currentSize);

    // Move inward for the next iteration
    currentSize -= step * 2;
    startX += step;
    startY += step;
  }

  endShape();
}


// Diamonds within Diamonds
function drawDiamondsInDiamond(x, y, size) {
  let dSize = size;
  strokeWeight(2);
  if (currentMode === 0) {
    // Regular mode: Use random colors from the regular palette
    stroke(random(colors));
  } else if (currentMode === 1) {
    // Colorful mode: Use colors from the colorfulPalette
    stroke(random(colorfulPalette));
  } else if (currentMode === 2) {
    // Random Size Mode: Use random colors from the regular palette
    stroke(random(colors));
  }
  noFill();
  for (let i = 0; i < 5; i++) {
    beginShape();
    vertex(0, -dSize / 2);
    vertex(dSize / 2, 0);
    vertex(0, dSize / 2);
    vertex(-dSize / 2, 0);
    endShape(CLOSE);
    dSize *= 0.7;
  }
}


// Bezier Curves
function drawCurves(x, y, size) {
  noFill();
  if (currentMode === 0) {
    // Regular mode: Use random colors from the regular palette
    stroke(random(colors));
  } else if (currentMode === 1) {
    // Colorful mode: Use colors from the colorfulPalette
    stroke(random(colorfulPalette));
  } else if (currentMode === 2) {
    // Random Size Mode: Use random colors from the regular palette
    stroke(random(colors));
  }
  strokeWeight(3);
  for (let i = 0; i < 6; i++) {
    bezier(
      -size / 2,
      -size / 2,
      random(-size, size),
      random(-size, size),
      random(-size, size),
      random(-size, size),
      size / 2,
      size / 2
    );
  }
}

 

Introducing Modes

To enhance user engagement, I implemented multiple visual modes (Regular, Colorful, Randomized, and Monochrome), allowing diverse experiences based on user interaction.

      • I utilized a currentMode variable to switch between visual styles seamlessly.
function draw() {
  // Set up the background for the current mode if needed
  if (frameCount === 1 || (currentCol === 0 && currentRow === 0)) {
    setupBackground(); // Set up the background for the current mode
  }

  // Analyze the frequency spectrum
  spectrum = fft.analyze();

  // Average the bass frequencies for a stronger response
  let bass = (spectrum[0] + spectrum[1] + spectrum[2]) / 3;

  // Log bass to check its values
  console.log(bass);

  // Map bass amplitude for size variation and oscillation
  let sizeVariation = map(bass, 0, 255, 0.8, 1.2);
  let amplitude = map(bass, 0, 255, 0, 1); // Normalize to [0, 1]

  // Use sine wave for oscillation based on time
  let time = millis() * 0.005; // Control the speed of oscillation
  let oscillation = sin(time * TWO_PI) * amplitude * 50; // Scale the oscillation

  // Calculate position in the grid
  let x = currentCol * cellSize;
  let y = currentRow * cellSize;

  // Apply the logic depending on currentMode
  if (currentMode === 0) {
    // Regular mode
    if (currentRow % 3 === 0) {
      drawZigZagPattern(
        x + cellSize / 2,
        y + cellSize / 2 + oscillation,
        cellSize
      ); // Draw zigzag on 3rd row with oscillation
    } else {
      let patternIndex = (currentCol + currentRow * cols) % patterns.length;
      drawPattern(x, y + oscillation, patternIndex); // Default pattern with oscillation
    }
  } else if (currentMode === 1) {
    // Colorful mode - only use colors from colorfulPalette
    let patternIndex = (currentCol + currentRow * cols) % patterns.length;
    drawColorfulPattern(x, y + oscillation, patternIndex); // Apply oscillation
  } else if (currentMode === 2) {
    // Random Size mode
    let patternIndex = (currentCol + currentRow * cols) % patterns.length;
    let randomSize = random(0.5, 1.5) * cellSize; // Random size
    drawPattern(x, y + oscillation, patternIndex, randomSize); // Apply oscillation
  } else if (currentMode === 3) {
    // Alternating Patterns
    drawAlternatingPatterns(x, y + oscillation, currentCol); // Apply oscillation
  }

  // Move to the next cell
  currentCol++;
  if (currentCol >= cols) {
    currentCol = 0;
    currentRow++;
  }

  if (currentRow >= rows) {
    noLoop(); // Stop the loop when all rows are drawn
  }
}

function setupBackground() {
  let colorModeChoice = int(random(3)); // Randomize the choice for background color

  if (currentMode === 0 || currentMode === 1 || currentMode === 2) {
    // Regular, Colorful, and Random Size Modes
    if (colorModeChoice === 0) {
      background(255); // White background
      stroke(0); // Black stroke
    } else if (colorModeChoice === 1) {
      background(0); // Black background
      stroke(255); // White stroke
    } else {
      background(50, 25, 0); // Dark brown background
      stroke(255, 165, 0); // Orange lines
    }
  } else if (currentMode === 3) {
    // Alternating Patterns Mode
    if (colorModeChoice === 0) {
      background(255); // White background
      stroke(0); // Black stroke
    } else if (colorModeChoice === 1) {
      background(0); // Black background
      stroke(255); // White stroke
    }
    // No stroke if colorModeChoice is 2 (do nothing)
  }
}

// Regular draw pattern function
function drawPattern(x, y, patternIndex, size = cellSize) {
  if (patterns[patternIndex]) {
    push();
    translate(x + size / 2, y + size / 2); // Center the pattern
    patterns[patternIndex](0, 0, size); // Draw the pattern using the provided size
    pop();
  }
}

// Draw patterns in colorful mode using only colors from colorfulPalette
function drawColorfulPattern(x, y, patternIndex) {
  let chosenColor = random(colorfulPalette); // Choose a color from colorfulPalette
  stroke(chosenColor); // Set stroke color
  fill(chosenColor); // Set fill color for the colorful patterns
  drawPattern(x, y, patternIndex); // Call the default drawPattern to handle the drawing
}

function drawAlternatingPatterns(x, y, col) {
  let patternIndex = col % patterns.length; // Alternate patterns based on column
  drawPattern(x, y, patternIndex);
}
Colorful mode with Music:

Music Integration

I integrated p5.js’s sound library to create an interactive experience where patterns respond to music. The FFT (Fast Fourier Transform) analyzes audio amplitude, allowing the symbols to offset based on the music. Essentially, once the music starts playing the symbols either go up, or down randomly based on the music, and  this alters the pattern drawn. So for each mode, there are two states, one where the music is playing and  one where it is not.

  •    I mapped bass frequencies to create lively, jittering movements.
let bass = (spectrum[0] + spectrum[1] + spectrum[2]) / 3;

let xOffset = random(-sizeVariation * 10, sizeVariation * 10);

let yOffset = random(-sizeVariation * 10, sizeVariation * 10);

drawPattern(x + xOffset, y + yOffset, patternIndex);

Achievements and Challenges

Achievements:

One of the achievements I am most proud of in this project is the implementation of multiple visual modes. I designed four distinct modes (Regular, Colorful, Randomized, and Monochrome) that allow users to experience the artwork in different ways. Each mode enhances user engagement and provides a unique perspective on the Adinkra symbols, making the project versatile and appealing. The smooth transitions between modes, triggered by key presses, add to the project’s interactivity and keep the viewer engaged.

Challenges:

Despite these successes, the journey was not without its challenges. One significant challenge was achieving a balance between the dynamic interaction of patterns and the constraints of the grid layout. Initially, the grid felt too rigid, making it difficult for the symbols to exhibit the desired randomness in their movements. To overcome this, I experimented with various techniques, such as introducing random offsets and modifying the size of the patterns to create a sense of organic movement within the structured grid. This iterative process taught me the importance of flexibility in design, especially when blending creativity with structured coding.

Another challenge was ensuring that each visual mode felt distinct and engaging. I initially struggled with mode transitions that felt too similar or jarring. By meticulously adjusting the visual elements in each mode—such as color schemes, pattern sizes, and overall aesthetics—I was able to develop a clearer identity for each mode. This process not only enhanced the user experience but also reinforced my understanding of how design choices can significantly impact perception and engagement.

Pen Plotting Translation and Process

The pen plotting process was straightforward yet time-consuming. Due to the dense nature of my project, I had to hide many layers to emphasize the vibrant colors of the patterns. While I didn’t change any code for plotting, I organized each layer by color to ensure a smooth plotting process. Overall, it took around two hours to complete!

Areas for Improvement and Future Work

Looking ahead, I aim to explore how to enhance the music’s impact on pattern dynamics. The grid structure, while beneficial, may limit randomness in movement. I’m excited to experiment with breaking down these constraints for more fluid interactions. Additionally, I dream of translating these patterns into fabric designs—what a fun endeavor that would be!

Resources:

https://www.masterclass.com/articles/sankofa-meaning-explained

Leave a Reply

Your email address will not be published. Required fields are marked *