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