Over the past few decades, neuroscientists and engineers have been collaborating to develop various technologies that integrate a deeper understanding of how the brain functions, making this knowledge accessible to everyday consumers. Today, we have seen an uprise in the number of people who possess technology such as an Apple Watch, Garmin, FitBit, Oura Ring and Whoop Band that can monitor heart rate (HR), heart rate variability (HVR), atrial fibrillation (AFib), and blood oxygen saturation (SpO2) measurements (Li et al., 2023). While these modalities have enabled regular consumers to take an active role in their health, they are missing out on technological advancements that could be utilized by consumers to further track their own health. With this, I have designed the NeuroBuds. This earbud enables richer, more deeply ingrained health monitoring, allowing consumers to regain some control over the monitoring and regulation of their health.
NeuroBuds: A New Wearable, Easy-to-Use Brain Monitor
Introduction
Hardware Design and Form Factor
The NeuroBuds are earbuds that sit in the ear, much like your typical Apple AirPods. They are made from a matte polymer material and are engraved with a laser-etched neural pattern to help the earbuds stay in place. Further, all the various sensors are housed in transparent polymer windows, allowing the embedded circuitry and electrodes to be visible, ensuring precision and accuracy in readings. The gold-plated electrodes help optimize conductivity to pick up the signal (Berto 2025). The tips are malleable to fit the ear canal, featuring a memory foam-like structure to keep sensors secure. Additionally, three different sizes are available to accommodate small, medium, and large ear canals, ensuring the most secure fit. Finally, they are splash-resistant to help protect against sweat and elements like light rain that may come into contact with the earbuds. These Neurobuds will weigh approximately 6 grams to maximize comfort and prevent the feeling of them weighing down the ear. Overall, these features provide a relatively durable design while ensuring comfort for the consumer.
Battery Life and Connectivity
Optimally, the NeuroBuds should last around 8-10 hours on a single charge. With the combined charge provided by the case, they should last for 30-40 hours, allowing for 3-4 days of wear without needing to be recharged. The in-ear system would be able to display data in real-time wirelessly through Bluetooth communication and a phone/web application, which will display the data acquired by the NeuroBuds (Salleh et al., 2024). As Salleh et al. describe, there will be an Application Programming Interface (API) that acts as a bridge, as follows:
This API handles the requests such as establishing connection, sending and receiving data and commands between the earbud BLE device and the web application. This API handles the data formatting to ensure the data exchanged between the web application and earbud is correct.
Core Technology and Measurements
Looking more closely at the technology inside these earbuds, we will first examine their brain monitoring capabilities using electroencephalogram (EEG). The EEG system inside these earbuds is designed to help track cognitive and emotional states, stress, focus, fatigue, neurological activity, and seizure detection. As shown in Figure 1, the dry gold EEG electrodes are embedded under a transparent polymer window. This will sit nicely in the concha region of the ear, allowing it to make contact with the skin and enabling precise measurements to be recorded. These electrodes have a multi-channel setup, with one measuring the concha through the ear canal and another measuring the concha behind the ear. With these placements, the electrodes are well-positioned to capture brain wave frequencies ranging from 0.5 to 30 Hz. While these earbuds are not specifically designed for sleep, if worn during sleep, these electrodes can track deep sleep patterns (delta); however, with the NeuroBuds being designed for more daily use, they can track theta (meditative and memory encoding), alpha (relaxation, calm), and beta (focus, problem solving) frequencies incredibly well. In an article by Athavipach, Pan-ngum, and Israsena (2019), they describe how in-ear technology can be beneficial for emotion classification and monitoring. The authors acknowledge that EEG has demonstrated high accuracy in emotion classification; however, most previous classifications have been conducted using impractical EEG headsets that are not suitable for continuous monitoring and daily use. After experimental testing, they found that “ in-ear EEG signal was verified to be highly correlated to the nearby T7 and T8 scalp EEG signals,” making in-ear EEG a good candidate for future use. Additionally, studies such as Joyner et al. (2024) demonstrate exceptional promise for the ability of in-ear EEG to detect focal-onset seizures. Designing an in-ear technology with electrodes, they found that “ ear-EEG reliably captures temporal lobe seizures (86% for intracranial patients and 100% for scalp patients), which account for approximately 40% of all forms of epilepsy.” Further, they made the following observations:
On average, seizure annotation onsets made using ear-EEG signals preceded annotation onsets of the same event made using scalp EEG. This observation further supports the hypothesis that the anatomical vantage point of the ear canals could be complementary, if not superior, to the analogous sensing locations of the scalp modality.This shows outstanding promise for ear-EEG technology, and NeuroBuds has implemented the technology through electrode placement to detect temporal lobe seizure onset.
Photoplethysmography (PPG) System
Next, the NeuroBuds have both a primary and secondary reflected photoplethysmography (PPG) array. The primary PPG is located at the base of each earbud near the bottom halo to create contact with the skin by the tragus. Here, a green light (525 nm) will be used to detect HR and HRV (Maeda, Sekine, & Tamura, 2011), a red light (660 nm) for SPO2, and an infrared light (880 nm) to track glucose trends (Hammour & Mandic, 2023) and maintain temperature stability through its deep penetration. The secondary PPG array will be placed on the outer surface of the earbud inside the transparent polymer, making contact with the concha and will only emit green and infrared light. In an article by Přibil, Přibilová, and Frollo (2020), the differences between transmitted PPG and reflected PPG were analyzed. In reflected PPG, both the light source, which is typically an LED, and the photodiode detector are mounted side by side. The LED emits a wavelength of light that penetrates the skin, and the light scatters through the skin tissue and blood vessels; a portion of the light is then reflected back to the detector. A transmitted PPG will send the light from the LED through a body part, where a detector on the other side will pick up the amount of light that made it through. Their experiment tested a transmitted PPG sensor through an ear clip, which did not provide great signal results. The NeuroBuds, however, will utilize a reflected PPG system, which is expected to work more effectively on curved skin areas, such as the ear canal and outer ear. Its photodiodes are highly sensitive and have ambient-light rejection to increase precision. Salleh et al., in their 2024 article, discuss how using an in-ear PPG system appears to be more effective than on a fingertip or wrist, as there are fewer motion artifacts to consider. It is also very important to ensure that the earbuds have sufficient contact and stability to record the signal as clearly as possible. The NeuroBuds also have an integrated temperature sensor that aids calibration and corrects the signal. Additionally, going back to the Hammour and Mandic 2023 study, they found that using an 880 nm LED inside the ear and using machine learning to train the system, “Statistically significant features were extracted from the PPG signals in order to capture the relationship between the PPG waveform and the reference glucose measurement from a commercially available glucometer.” With the 880 nm LED and photodiode, along with calibration and training through the phone application, the NeuroBuds have the capacity to aid individuals with diabetes and track whether they appear to be experiencing hypoglycemia or hyperglycemia based on the stored knowledge the application uses to determine when the output trend is consistent with these conditions.
Microphone Array and Emotion Detection
NeuroBuds also feature a triple-microphone array to detect voice, breathing, and emotions. The first microphone is positioned on the stem tip, facing towards the neck, and measures acoustic vibrations conducted from the body, such as subtle respiratory movements. It provides reference noise for noise cancelling and can detect low-frequency physiological sounds. This works in conjunction with the PPG and EEG systems for multi-sensory respiratory analysis, as well as supporting the detection of glucose and stress interference through respiratory tracking. The second microphone is positioned on the outer mic, facing towards the outside world, to capture environmental noise for noise cancellation, transparency mode, and to isolate the user's speech. This significantly aids PPG and EEG in providing a baseline for artifact rejection. Finally, the NeuroBuds feature an inner canal microphone at the tip that is inserted into the ear, capturing bone-conducted and internal vibrations, as well as jaw clenching, grinding, and breathing changes. This enables emotion detection through jaw tension, vocal tremors, EEG, and HRV, as well as monitoring of breathing rhythm. Röddiger et al. (2022) survey notes that devices worn in the ear “are located in close proximity to a number of important anatomical structures including the brain, blood vessels, and facial muscles,” and that “the inner ear cavity acts as an echo chamber to amplify internal body sounds,” supporting the use of the NeuroBuds triple mic array to capture vocal features, the environment, jaw and respiration behaviours, and other acoustic signals from the body.
LED Halo and Biofeedback
Lastly, the NeuroBuds feature a 360-degree LED halo with biofeedback and visualization that wraps around the bottom of the earbuds. Using EEG, PPG, and microphone data, the earbuds will then display brain-body feedback through the colours presented. A blue LED halo shows that the user is focused and calm. A white presentation indicates that the user is engaged and alert. Finally, an amber halo showcases that the user is experiencing stress and is overwhelmed. These trends can be tracked through the app, alerting the user if they are showing signs of distress and inviting them to engage in meditation or breathing exercises, or to view their productivity scores and track improvements.
User Experience and Software
On the application interface itself, users can view various graphs, pie charts, and visual displays to analyze their patterns. The opening panel will be customizable, allowing users to select the metrics that are most valuable and relevant to them for display. This may include their heart rate, blood glucose levels, and focus level, among others. Furthermore, the home display will also feature a Quick Actions panel that prompts users to perform actions such as meditating or checking their other trends, leading them to other pages, including their Brainwave Insights, Physiology, Biofeedback, and Training. These separate pages will house more specific metrics and include detailed graphs displaying different brainwave frequencies, heart rate trends, mind wandering events, focus durations, and meditative orbs to engage in training, among other features. As long as the application remains open on your phone, the feedback you receive will be live, updating constantly as you go about your day.
Gamification: NeuroBuddy
While all of these trends alone might be enough to fascinate the user and keep them engaged, there is also a gamified aspect to the application called NeuroBuddy —a little pet that mirrors your mental states and health metrics, where your states directly impact the happiness, energy, and fashion of your pet. You get to design your own tasks to fit your goals, with the system making suggestions if you frequently meet a goal and believes you should try to increase it, or even suggesting lowering the goal if it becomes too unattainable. Meeting these goals will earn you MindCoins, which you can use to purchase outfits, different environments, auras, colours, or food that gives you a boost in MindCoins. Furthermore, you will also see your pet's behaviour change in real-time based on your current metrics. For instance, if you have a steady alpha rhythm, your pet may calmly curl up, whereas if you are experiencing a higher heart rate, jaw clenching, and increased respiration rate, the pet may frown or have a smaller aura orb.
Validation and Scientific Credibility
To ensure that consumers receive a reliable product, NeuroBuds must reflect the physiological features they claim to demonstrate (construct validity), meet the standards in their measurements (criterion validity), function effectively in real-world settings (ecological validity), and be reliable for various users. Firstly, testing would begin with the hardware of the NeuroBuds alone, examining the materials under various conditions, including different temperatures, light distances, simulated movements, and stimulated seizure data (Joyner et al., 2024), as well as other factors, to ensure the hardware meets the standards. After the hardware itself has been thoroughly tested, experiments will then begin with human participants in the lab, testing EEG and PPG to see if these measurements are valid against other mechanisms like scalp EEG ( Joyner et al., 2024), finger PPG (Salleh et al., 2024), and glucose monitoring (Hammour & Mandic, 2023). Once these lab tests are clear and show significant results, field testing will begin, where people will have the opportunity to use NeuroBuds in real-world settings and provide self-reports to compare the measurements made by the NeuroBuds with their subjective experiences. After these are all cleared, NeuroBuds will be able to be sold for more practical daily use while clinical testing takes place for the potential for NeuroBuds to have medical-grade glucose monitoring and seizure detection. To avoid NeuroBuds becoming junk science, transparency and honesty must remain with consumers. NeuroBuds will be marketed for wellness and trend monitoring, and the study results will be shared and accessible to customers, ensuring they can view the comparative results across modalities (scalp EEG, finger PPG). In remaining honest, we will be transparent about the variability between people with different skin tones, ear shapes, and how much you move while wearing them, which may influence the results generated.
Business and Marketing Reality
When analyzing the cost compared to Apple AirPod manufacturing, as discussed in the article “ How much does it cost to manufacture AirPods Pro?” (2025), it likely costs Apple around $60-$80 USD to manufacture AirPods, excluding their costs for research, development, and marketing. Given the additional features NeuroBuds have, my cost to manufacture alone is likely around $80-$100 US. For NeuroBuds' first release as a wellness product, the market cost will be approximately $250-$300 USD or $350-$400 CAD. Marketing itself will be targeted towards wellness enthusiasts, students, parents, and those who, in general, want a clearer insight into their health while being able to listen to music, an audiobook, a podcast, or other audio content. This allows the monitor to blend seamlessly with everyday life while also tracking beneficial metrics. All data collected will be completely private and securely stored within the app, behind security walls, to ensure that other companies cannot access or use the data freely. Further, the application will have passcode or Face ID protection to ensure that only you can log on and access your data. Any data stored by NeuroBuds is encrypted, ensuring that each set of data cannot be directly traced back to a specific user. Some of my biggest competitors will be with companies including NeuroSense, which has developed earbuds with built-in EEG sensors (NextSense Smartbuds) and Alpha Wearables, with their 3-electrode EEG, carotid HR, HRV, and SPO2 tracking (The World Smallest Mind Tracker Device).
References
- Athavipach, C., Pan-ngum, S., & Israsena, P. (2019). A wearable in-ear EEG device for emotion monitoring. Sensors, 19 (18), 4014. https://doi.org/10.3390/s19184014
- Berto, M. (2025, July 31). Dry-contact EEG electrodes: Materials, trade-offs, and our choice. BrainAccess. https://www.brainaccess.ai/dry-contact-eeg-electrodes-materials-trade-offs-and-our-choice/
- Hammour, G., & Mandic, D. P. (2023). An in-ear PPG-based blood glucose monitor: A proof-of-concept study. Sensors, 23 (6), 3319. https://doi.org/10.3390/s23063319
- How much does it cost to manufacture airpods pro? Manufacturing. (2025, May 30). https://www.sohoify.com/how-much-does-it-cost-to-manufacture-airpods-pro-guide/?utm_source=chatgpt.com#why-is-there-such-a-big-markup?
- Joyner, M., Hsu, S.-H., Martin, S., Dwyer, J., Chen, D. F., Sameni, R., Waters, S. H., Borodin, K., Clifford, G. D., Levey, A. I., Hixson, J., Winkel, D., & Berent, J. (2024). Using a Standalone Ear-EEG Device for Focal-Onset Seizure Detection. Bioelectronic Medicine, 10 (1). https://doi.org/10.1186/s42234-023-00135-0
- Li, K., Cardoso, C., Moctezuma-Ramirez, A., Elgalad, A., & Perin, E. (2023). Heart rate variability measurement through a smart wearable device: Another breakthrough for Personal Health Monitoring? International Journal of Environmental Research and Public Health, 20 (24), 7146. https://doi.org/10.3390/ijerph20247146
- Maeda, Y., Sekine, M., & Tamura, T. (2010). The Advantages of Wearable Green Reflected Photoplethysmography. Journal of Medical Systems, 35 (5), 829–834. https://doi.org/10.1007/s10916-010-9506-z
- NextSense Smartbuds. NextSense. (n.d.). https://nextsense.io/products/smartbuds/?utm_source=BusinessWire&utm_medium=PressRelease&utm_campaign=Funding
- Přibil, J., Přibilová, A., & Frollo, I. (2020). Comparative Measurement of the PPG Signal on Different Human Body Positions By Sensors Working in Reflection and Transmission Modes. 7th International Electronic Conference on Sensors and Applications, 69. https://doi.org/10.3390/ecsa-7-08204
- Röddiger, T., Clarke, C., Breitling, P., Schneegans, T., Zhao, H., Gellersen, H., & Beigl, M. (2022). Sensing With Earables. Proceedings of the ACM on Interactive, Mobile, Wearable and Ubiquitous Technologies, 6 (3), 1–57. https://doi.org/10.1145/3550314
- Salleh, N. I., Azudin, K., Gan, K. B., Ja’afar, M. H., & Mohamad, M. S. (2024). Remote cardiorespiratory monitoring with an in-ear PPG device. Springer Proceedings in Physics, 323–335. https://doi.org/10.1007/978-981-97-0142-1_32
- The World Smallest Mind Tracker device. Alpha Wearables. (n.d.). https://alphawearables.com/?utm_source=chatgpt.com#features
Other Course Work
📊 Assignment 1: EEG Analysis
📄 Lab Assignment 1 NeuroIm - Caitlyn Archibald.pdf
View Original PDFASSIGNMENT :
Step 1: Downloaded the Data Using Google Collab
Step 2: Created Plot Showing 10 Seconds Of “Raw” Brain Activity With Noise Included
Step 3: Applying a Filter to Clean Noise
Step 4: Made a Side-by-Side Comparison of Raw vs Filtered Data
Step 5: Power Spectrum Plots Showing Strongest Brain Wave Frequencies
🎨 Assignment 2: BrainImation
📄 The EEG Mind Garden (BrainImation Psych 403) - Caitlyn Archibald.pdf
View Original PDFAssignment 2: BrainImation — Create Your Brain-Controlled Animation
Just in case the saved JavaScript code does not work, here is the following code that was pasted
into the BrainImation website:
// Mind Garden Visualization 🌿
// Elegant Natural Flower Brainwave Display
// Flowers grow and animate based on EEG (simulated if no headset connected)
// ----------------------------- GLOBALS ----------------------------- //
let flowers = [];
let time = 0 ;
// EEG data structure
let eegData = {
delta: 0.2 ,
theta: 0.3 ,
alpha: 0.4 ,
beta: 0.5 ,
gamma: 0.6 ,
attention: 0.5 ,
meditation: 0.4 ,
connected: false
};
// ----------------------------- SETUP ----------------------------- //
function setup() {
colorMode( HSB , 360 , 100 , 100 , 1 );
textFont( 'Georgia' ); // Elegant serif font
textAlign( CENTER , CENTER );
textSize( 16 );
// Meaningful brainwave order
let waveOrder = [ 'delta' , 'theta' , 'alpha' , 'beta' , 'gamma' ];
let spacing = width / (waveOrder.length + 1 );
for ( let i = 0 ; i < waveOrder.length; i++) {
flowers.push({
x: spacing * (i + 1 ),
y: height * 0.55 ,
baseSize: random( 35 , 50 ),
currentSize: 0 ,
type: waveOrder[i]
});
}
}
// ----------------------------- DRAW MAIN ----------------------------- //
function draw() {
simulateEEG();
time += 0.01 ;
// Background soft light pink
background( 200 , 10 , 10 ); // pale rose
// Draw each flower
flowers.forEach(drawFlower);
// Draw labels
drawLabels();
// Connection prompt
if (!eegData.connected) {
fill( 0 , 0 , 30 , 0.7 );
textSize( 18 );
text( "Connect your Muse headset or use simulated data" , width / 2 , height * 0.1 );
}
}
// ----------------------------- FLOWERS ----------------------------- //
function drawFlower(flower) {
let brainValue = eegData[flower.type];
flower.currentSize = lerp(flower.currentSize, flower.baseSize + brainValue * 80 ,
0.02 );
// Stem
stroke( 110 , 60 , 40 );
strokeWeight( 5 );
line(flower.x, height, flower.x, flower.y + 30 );
push();
translate(flower.x, flower.y);
noStroke();
switch (flower.type) {
case 'beta' : drawSunflower(flower); break ;
case 'alpha' : drawDaisy(flower); break ;
case 'theta' : drawOrchid(flower); break ;
case 'delta' : drawCherryBlossom(flower); break ;
case 'gamma' : drawChrysanthemum(flower); break ;
}
pop();
}
// ----------------------------- FLOWER TYPES ----------------------------- //
- function drawSunflower(f) { fill( 45 , 90 , 90 ); for ( let i = 0 ; i < 20 ; i++) { rotate( TWO_PI / 20 ); ellipse(f.currentSize * 0.8 , 0 , f.currentSize * 0.85 , f.currentSize * 0.3 ); } fill( 30 , 60 , 40 ); ellipse( 0 , 0 , f.currentSize * 1.1 ); } function drawDaisy(f) { fill( 0 , 0 , 100 ); for ( let i = 0 ; i < 12 ; i++) { rotate( TWO_PI / 12 ); ellipse(f.currentSize * 0.8 , 0 , f.currentSize * 0.8 , f.currentSize * 0.35 ); } fill( 55 , 80 , 90 ); ellipse( 0 , 0 , f.currentSize * 0.8 ); } function drawOrchid(f) { push(); translate( 0 , 50 ); // sits directly on stem noStroke(); let petHeight = f.currentSize * 1.2 ; let petWidth = f.currentSize * 0.6 ; // Central petal fill( 290 , 60 , 80 ); beginShape(); vertex( 0 , 0 ); bezierVertex(-petWidth * 0.3 , -petHeight * 0.5 ,
- petWidth * 0.2 , -petHeight, 0 , -petHeight); bezierVertex(petWidth * 0.2 , -petHeight, petWidth * 0.3 , -petHeight * 0.5 , 0 , 0 ); endShape( CLOSE ); // Left petal fill( 295 , 55 , 85 ); beginShape(); vertex( 0 , 0 ); bezierVertex(-petWidth * 0.6 , -petHeight * 0.4 ,
- petWidth * 0.4 , -petHeight * 0.8 , 0 , -petHeight * 0.8 );
- bezierVertex( 0 , -petHeight * 0.8 ,
- petWidth * 0.2 , -petHeight * 0.8 , 0 , 0 ); endShape( CLOSE ); // Right petal fill( 285 , 55 , 85 ); beginShape(); vertex( 0 , 0 ); bezierVertex(petWidth * 0.6 , -petHeight * 0.4 , petWidth * 0.4 , -petHeight * 0.8 , 0 , -petHeight * 0.8 ); bezierVertex( 0 , -petHeight * 0.8 , petWidth * 0.2 , -petHeight * 0.4 , 0 , 0 ); endShape( CLOSE ); // Center lip detail fill( 300 , 80 , 90 ); ellipse( 0 , -petHeight * 0.9 , petWidth * 0.15 , petHeight * 0.15 ); pop(); } function drawCherryBlossom(f) { fill( 350 , 40 , 100 ); for ( let i = 0 ; i < 5 ; i++) { rotate( TWO_PI / 5 ); ellipse(f.currentSize * 0.8 , 0.2 , f.currentSize * 1.5 , f.currentSize * 0.5 ); } fill( 45 , 80 , 90 ); ellipse( 0 , 0 , f.currentSize * 0.7 ); } function drawChrysanthemum(f) { for ( let layer = 0 ; layer < 3 ; layer++) { fill( 200 , 100 , 90 , 1 - layer * 0.3 ); for ( let i = 0 ; i < 22 ; i++) { rotate( TWO_PI / 22 ); ellipse(f.currentSize * ( 0.4 + layer * 0.3 ), 0 , f.currentSize * 0.4 , f .currentSize * 0.2 ); } } } // ----------------------------- LABELS ----------------------------- // function drawLabels() {
fill( 100 , 10 , 100 ); // White
textSize( 16 );
flowers.forEach(f => {
let labelY = f.y + f.currentSize + 50 ;
let word = "" ;
let description = "" ;
if (f.type === "delta" ) { word = "delta" ; description = " sleep + restoration" ; }
if (f.type === "theta" ) { word = "theta" ; description = "meditate + creativity" ; }
if (f.type === "alpha" ) { word = "alpha" ; description = "calm flow state" ; }
if (f.type === "beta" ) { word = "beta" ; description = "focus + concentration" ; }
if (f.type === "gamma" ) { word = "gamma" ; description = "cognition +
info-process" ; }
text(word, f.x, labelY);
text(description, f.x, labelY + 20 );
});
}
// ----------------------------- EEG SIMULATION ----------------------------- //
function simulateEEG() {
eegData.delta = noise(time * 0.2 + 100 );
eegData.theta = noise(time * 0.2 + 200 );
eegData.alpha = noise(time * 0.2 + 300 );
eegData.beta = noise(time * 0.25 + 400 );
eegData.gamma = noise(time * 0.3 + 500 );
eegData.attention = noise(time * 0.4 + 600 );
eegData.meditation = noise(time * 0.25 + 700 );
eegData.connected = true ;
}
Flowers Representing The Strength of the EEG Signal
Flower Type EEG Wave Represents / Meaning Visual Notes
Delta Deep sleep /
restorative waves
Deep healing, restoration,
body recovery
Cherry Blossom – soft pink
petals, layered
Theta Creativity /
meditation waves
Dreams, creativity,
intuition
Orchid – gentle purple, sits
directly on stem
Alpha Relaxed alertness Calm flow state,
relaxation, presence
Daisy – white petals with
yellow center
Beta Active thinking /
focus
Focused energy,
concentration,
problem-solving
Sunflower – bright yellow,
radiant petals
Gamma Learning / insight
waves
Insight, clarity, cognitive
integration
Chrysanthemum – blue
layered, exotic bloom
The EEG Mind Garden
Beginning this assignment, I truthfully had no idea what I wanted to do. While many of the
animations on the website were impressive, none of them truly drew me in and sparked my
creativity. Feeling at a loss, I began experimenting with various examples on p5.js to explore
some of the capabilities coding had to offer. After experimenting, I simply started thinking about
things I enjoy and how I could incorporate them into something that could be showcased in code
and connect with the stimulated EEG data. Ultimately, I chose to create a Mind Garden using
BrainImation to represent the strength of the five different brain wave frequencies. I assigned a
different flower to each frequency of brain wave– these included, from left to right, a cherry
blossom for delta waves, an orchid for theta waves, a daisy for alpha waves, a sunflower for beta
waves, and a chrysanthemum for gamma waves. When pairing these flowers with the stimulated
EEG data, the more in tune the frequency of the brain is aligned with the frequency of a specific
brain wave, the larger the flower grows. On the contrary, the more distant the recorded brain
frequency is from a particular brain wave, the more the flower begins to shrink. For example, if I
were highly focused on a task, I would expect to see growth in my beta sunflower and shrinking
of the delta cherry blossom, as beta waves are present when focused and concentrating, while
delta waves are larger during deep sleep.
Throughout the completion of this assignment, I faced numerous challenges, including
difficulties connecting my Muse Headset to the BrainImation platform to utilize my own brain
frequencies instead of stimulating the data. Additionally, when I first asked ChatGPT to design
me some flowers to correspond with the stimulated EEG data, it filled my screen with 30
overlapping flowers with highly saturated colours, where the petals were unattached to the pistil,
and each flower was intensely vibrating. It looked like a 1960s hippie had vomited onto my
screen and was having a seizure. Further, when ChatGPT was generating new code for me, it
kept appearing up black on the BrainImation platform. After continuously telling ChatGPT that
my screen was black, it eventually figured out that I needed to clear the code prompt of
createCanvas , as the BrainImation/ p5.js platform was already creating a canvas for me. Once I
learned how to remove that instruction from the code it generated, I was able to see my creations.
With consistent prompting to reduce the amount of flowers to the corresponding brain
frequencies, differentiating the flowers, adding labels to clarify what was being represented,
making clear descriptions of what each area of the code corresponded to so I could edit specific
characteristics myself, and adjusting the colours of the flowers to be more indicative of the
particular flower type, I was able to create the beautiful Mind Garden that you can see in the
video. This taught me that with persistent prompting and asking specific questions, the AI system
was greatly able to assist me and modulate the code to include the details I was looking for, even
if it generated some questionable images at times. One of the most valuable things I asked
ChatGPT to do for me was to include the clear labels of what each segment of code related to.
This allowed me to learn more about coding and how adjusting different factors affected the
image presented to me. Many of the colours, title labels, and spacing were hand-edited by me, as
ChatGPT often wasn’t able to make changes adequately, such as moving the flower so it was
attached to the stem or adjusting the size of the pistil so that it was touching the flower petals.
This gave me the unique opportunity to hand-edit these various characteristics, knowing
precisely what would be modified.
🎥 BrainImation Flower EEG Recording - Caitlyn Archibald.mp4
💡 Videos require Google Drive access. Open in new tab if it doesn't load.
🎯 Midterm Project
📄 Caitlyn Archibald Psych 403 Midterm - Caitlyn Archibald.pdf
View Original PDFCaitlyn Archibald Psych 403 Midterm - Part 1
In the first part of the Midterm, I have created a neurofeedback model that trains the brain to
promote higher levels of attention and meditation. For this, we directly observe the alpha and
theta waves in the stimulated data, setting the target result to produce alpha waves twice as
strong as the theta waves. Since the goal is for the participant to reach a more relaxed, meditative
state, there should be more alpha waves, as they are associated with relaxed focus and meditative
states. In contrast, theta waves are associated with drowsier, deeper states of relaxation. This
training occurs using the orb in the centre of the screen. When a non-rotating, turquoise/teal orb
is present, it indicates low levels of attentiveness and meditation; however, it begins rotating (in
meditation) and turns a beautiful, deeper jean blue (in attention) as these levels increase.
Furthermore, the length of the petals, along with the size of the orb, increases as progress is made
toward adopting the alpha state. Finally, every time the target alpha state is reached for a
sustained duration of 2 seconds, there are white celebratory particles that pop out from the center
of the orb. These various changes make this code significantly different from the code generated
for our previous assignment. In the past assignment, the visualization on the screen simply
responded to whatever brain frequency bands were present. In this almost gamified visualization,
the design promotes a change in the frequency band that we want to be present in– in this case, I
designed it to try to manipulate a participant's brain wave frequency by encouraging them to
have more alpha frequency bands present by entering a more meditative state to make the orb
have an elegant, meditative spin while muting the vibrant teal to a more calming, less saturated
shade of blue. What I learned from this is that coding for it made me want to rip my hair out and
scream violently. I tried many game options and experimented with sounds, but nothing worked
out as I had intended. Small changes I made continuously caused the whole code to stop
working, or changing just one aspect of the code made everything else stop working. This was
the only design I seemed to be able to successfully get to function in a way that aligned with the
midterm goals and still looked elegant.
Caitlyn Archibald Psych 403 Midterm - Part 2
In this second part of the midterm, I decided to design an Event-Related Potential (ERP)
experiment that measured the N170, an ERP with a negative deflection around 170ms after
stimulus onset. Specifically, the N170 is usually observed during the presentation of faces, which
humans are experts at identifying; however, this negative deflection can also be observed when
an individual is shown anything they are an expert in. Therefore, the stimulus manipulation I
adopted displayed faces with three facial expressions: happy (smiling face), sad (frowning face),
and surprise (open mouth). This was contrasted with the presentation of three non-facial stimulus
objects: a triangle, a square, and a star. Every time one of these stimuli appears, a 1-second epoch
(200 ms at baseline and 800 ms after stimulus onset) is captured and categorized as a face trial if
a face was presented or an object trial if an object was presented. Each category can store 20
total epoch trials. Next, the baseline is corrected to ensure that each captured epoch begins at the
same 0 ms starting point, meaning that the pre-stimulus period voltage is removed from the
epoch data. The trials are then averaged at each point across trials and finally separated by
stimulus and EEG channel: TP9-face, TP9-object, TP10-face, or TP10-object. The harsh white
line at 0ms marks the location of stimulus onset, followed by a tick mark every 100ms. While it
is challenging to see in the data, there should be an identifiable negative deflection around 170ms
after a facial stimulus presentation, specifically in the TP10 EEG channel, as this is the
temporal-parietal area in the right hemisphere that responds more strongly to facial stimuli. By
averaging these trials, the portion of background noise influencing the data is reduced, yielding a
much more precise visual representation of the N170. If this information were captured from raw
EEG data, there would be much more precision, as currently, the randomly stimulated data can
just be continuously generating random noise rather than a genuine signal. This is why, using the
stimulated data, it’s challenging to see a clear N170. With real EEG data, a clear negative
inflection around 170 ms after stimulus presentation should be visible when faces are shown
compared to objects, especially in the superior temporal sulcus.
Midterm Part 1 Code (just in case the .txt file doesn’t work)
// EEG
let orb = { x: 0 , y: 0 , size: 50 , glow: 0 };
let targetAlphaThetaRatio = 2 ;
let rewardActive = false ;
let rewardTimer = 0 ;
let rewardDuration = 60 ;
let rewardParticles = [];
let score = 0 ;
let maxScorePerFrame = 0.05 ;
let rotation = 0 ;
function setup() {
orb.x = width / 2 ;
orb.y = height / 2 ;
colorMode( HSB , 360 , 100 , 100 , 1 );
}
// ----------------------------------------------------
// 🧠 Brainwave progress calculation
// ----------------------------------------------------
function getProgress() {
let currentRatio = eegData.alpha / (eegData.theta + 0.01 );
let diff = abs(targetAlphaThetaRatio - currentRatio);
let progress = constrain( 1 - diff / targetAlphaThetaRatio, 0 , 1 );
return progress;
}
// ----------------------------------------------------
// ✨ Calm Celebration Effect (Glitter Particles Only)
// ----------------------------------------------------
function triggerReward() {
rewardActive = true ;
rewardTimer = rewardDuration * 2 ; // lasts longer
// Create a batch of drifting lavender-white particles
for ( let i = 0 ; i < 60 ; i++) {
rewardParticles.push({
x: orb.x,
y: orb.y,
vx: random(- 0.8 , 0.8 ),
vy: random(- 1.5 , - 0.2 ),
size: random( 4 , 8 ),
hue: random( 220 , 280 ), // cool white–lavender range
life: 120
});
}
}
function drawReward() {
if (!rewardActive) return ;
// Drifting glow particles only
for ( let p of rewardParticles) {
fill(p.hue, 20 , 100 , p.life / 120 ); // low saturation = soft glow
noStroke();
ellipse(p.x, p.y, p.size);
p.x += p.vx;
p.y += p.vy;
p.life--;
}
rewardParticles = rewardParticles.filter(p => p.life > 0 );
rewardTimer--;
if (rewardTimer <= 0 && rewardParticles.length === 0 ) rewardActive = false ;
}
// ----------------------------------------------------
// 🌅 Gradient Background (Top → Bottom, Vibrant)
// ----------------------------------------------------
function drawBackground() {
let topColor = color( 180 , 50 , 90 ); // light blue
let bottomColor = color( 220 , 50 , 90 ); // blueish-purple
for ( let y = 0 ; y < height; y++) {
let inter = map(y, 0 , height, 0 , 1 );
let c = lerpColor(topColor, bottomColor, inter);
stroke(c);
line( 0 , y, width, y);
}
}
// ----------------------------------------------------
// Snowflake/Flower (Soft, Subtle Petal Motion, Harmonized Colors)
// ----------------------------------------------------
function drawSunflower(x, y, baseLength, petalWidth, progress, meditation,
colorProgress ) {
push();
translate(x, y);
rotation += map(meditation, 0 , 1 , 0 , 0.02 );
rotate(rotation);
// Harmonized purple → pink for petals
let startHue = 180 ; // brighter purple
let endHue = 200 ; // pink-magenta
let hueValue = lerp(startHue, endHue, colorProgress);
let petalSat = 90 ;
let petalBright = 90 ;
let petalCount = 15 ;
// 🌬 Gentle ambient "breathing"
let softWave = sin(frameCount * 0.01 ) * 0.05 ;
for ( let i = 0 ; i < petalCount; i++) {
let angle = TWO_PI / petalCount * i;
let length = baseLength * ( 0.9 + 0.15 * progress + softWave);
let width = 80 ;
push();
rotate(angle);
fill(hueValue, petalSat, petalBright);
noStroke();
beginShape();
vertex( 0 , 0 );
bezierVertex(width/ 4 , -length/ 3 , width/ 4 , - 2 *length/ 3 , 0 , -length);
bezierVertex(-width/ 4 , - 2 *length/ 3 , -width/ 4 , -length/ 3 , 0 , 0 );
endShape( CLOSE );
pop();
}
// Second offset layer
for ( let i = 0 ; i < petalCount; i++) {
let angle = TWO_PI / petalCount * i + PI /petalCount;
let length = baseLength * ( 0.85 + 0.1 * progress + softWave);
let width = petalWidth * 0.9 ;
push();
rotate(angle);
fill(hueValue, petalSat, petalBright * 0.95 );
noStroke();
beginShape();
vertex( 0 , 0 );
bezierVertex(width/ 4 , -length/ 3 , width/ 4 , - 2 *length/ 3 , 0 , -length);
bezierVertex(-width/ 4 , - 2 *length/ 3 , -width/ 4 , -length/ 3 , 0 , 0 );
endShape( CLOSE );
pop();
}
// Core
let coreSize = petalWidth * 1.5 + progress * 10 ;
fill((hueValue + 30 ) % 360 , 80 , 50 + progress * 20 );
ellipse( 0 , 0 , coreSize);
pop();
}
// ----------------------------------------------------
// 🌟 Main Draw Loop
// ----------------------------------------------------
function draw() {
drawBackground();
let progress = getProgress();
// 🌬 Calm-state Orb Behaviour
let baseSize = 50 ;
let pulseAmplitude = map(progress, 0 , 1 , 0 , 20 );
let pulseSpeed = map(progress, 0 , 1 , 0 , 0.08 );
let pulse = sin(frameCount * pulseSpeed) * pulseAmplitude;
orb.size = baseSize + progress * 50 + pulse;
// 🩵 Orb hue: bright purple → pink
let colorProgress = eegData.attention;
let lowHue = 260 ; // brighter purple at low attention
let highHue = 330 ; // pink at high attention
orb.hue = lerp(lowHue, highHue, colorProgress);
// 🌫 Soft glow layers
noStroke();
for ( let i = 5 ; i > 0 ; i--) {
fill(orb.hue, 70 , 100 , progress * 0.2 / i);
ellipse(orb.x, orb.y, orb.size + i * 15 );
}
// 🌻 Draw sunflower
drawSunflower(orb.x, orb.y, orb.size * 2 , orb.size * 0.25 , progress,
eegData .meditation, colorProgress);
// 🎁 Rewards
if (progress > 0.8 && !rewardActive) triggerReward();
if (rewardActive) {
orb.size += sin(frameCount * 0.3 ) * 5 ; // gentle bloom during celebration
}
drawReward();
// 🧾 Long-term Score
score += progress * maxScorePerFrame;
score -= ( 1 - progress) * 0.01 ;
score = max(score, 0 );
fill( 0 , 0 , 100 );
textSize( 20 );
textAlign( RIGHT , TOP );
text( "Score: " + floor(score), width - 20 , 20 );
}
Midterm Part Two Code (just in case the .txt file doesn’t work)
// ERP Experiment: Faces vs Objects (N170-focused)
let channels = [ 'TP9' , 'TP10' ]; // only temporal-parietal electrodes relevant for N170
let colors = { face: [ 270 , 80 , 90 ], object: [ 180 , 80 , 90 ] }; // H, S, B
let epochLength = 256 ; // 1 second epochs at 256 Hz
let preStim = 51 ; // ~200 ms baseline
let postStim = 205 ; // ~800 ms post-stimulus
let totalSamples = preStim + postStim;
let faceTrials = { TP9 : [], TP10 : [] };
let objectTrials = { TP9 : [], TP10 : [] };
let maxTrials = 20 ;
let trial = 0 ;
let showFace = true ; // alternate stimuli (true = face this trial)
let stimFrames = 12 ; // ~200 ms
let itiFrames = 24 ; // ~400 ms ITI
let stimCounter = 0 ;
let stimState = 0 ; // 0=ITI, 1=Stimulus
// Object and face randomization
let objectTypes = [ 'square' , 'triangle' , 'star' ]; // ✅ replaced circle with star
let currentObject = 'square' ;
let faceTypes = [ 'happy' , 'sad' , 'surprised' ];
let currentFace = 'happy' ;
function setup() {
frameRate( 60 );
colorMode( HSB , 360 , 100 , 100 );
textSize( 14 );
}
function draw() {
background( 0 , 0 , 10 );
// --- Stimulus state machine ---
stimCounter++;
if (stimState === 0 && stimCounter >= itiFrames) {
stimState = 1 ;
stimCounter = 0 ;
// Randomize stimulus variant
if (showFace) {
currentFace = random(faceTypes);
} else {
currentObject = random(objectTypes);
}
// Capture EEG epoch
channels.forEach((ch) => {
let epoch = eegData.getRawChannel(ch, totalSamples);
if (epoch.length === totalSamples) {
if (showFace) {
faceTrials[ch].push(epoch);
if (faceTrials[ch].length > maxTrials) faceTrials[ch].shift();
} else {
objectTrials[ch].push(epoch);
if (objectTrials[ch].length > maxTrials) objectTrials[ch].shift();
}
}
});
} else if (stimState === 1 && stimCounter >= stimFrames) {
stimState = 0 ;
stimCounter = 0 ;
showFace = !showFace;
trial++;
}
// --- Top panel: ERP (N170-focused) ---
let topY = 50 , panelH = 200 ;
let channelSpacing = panelH / channels.length;
strokeWeight( 1.5 );
noFill();
let offsetAmount = 5 ;
channels.forEach((ch, c) => {
let avgFace = averageEpoch(faceTrials[ch]);
let avgObject = averageEpoch(objectTrials[ch]);
let yOffset = topY + c * channelSpacing + channelSpacing / 2 ;
let lineWeight = ch === 'TP10' ? 2.5 : 1.5 ;
if (avgFace) {
stroke(colors.face[ 0 ], colors.face[ 1 ], colors.face[ 2 ], 200 );
strokeWeight(lineWeight);
beginShape();
for ( let i = 0 ; i < avgFace.length; i++) {
let x = map(i, 0 , totalSamples - 1 , 50 , width - 20 );
let yFace = yOffset + map(avgFace[i], - 100 , 100 , -channelSpacing / 2 + 10 ,
channelSpacing / 2 - 10 ) - offsetAmount;
vertex(x, yFace);
}
endShape();
}
if (avgObject) {
stroke(colors.object[ 0 ], colors.object[ 1 ], colors.object[ 2 ], 200 );
strokeWeight(lineWeight);
beginShape();
for ( let i = 0 ; i < avgObject.length; i++) {
let x = map(i, 0 , totalSamples - 1 , 50 , width - 20 );
let yObject = yOffset + map(avgObject[i], - 100 , 100 , -channelSpacing / 2 + 10 ,
channelSpacing / 2 - 10 ) + offsetAmount;
vertex(x, yObject);
}
endShape();
}
fill( 0 , 0 , 100 );
noStroke();
textAlign( LEFT , CENTER );
text(ch, 10 , yOffset);
stroke( 0 , 0 , 20 );
line( 50 , yOffset + channelSpacing / 2 , width - 20 , yOffset + channelSpacing / 2 );
});
// 0 ms vertical line
stroke( 0 , 0 , 90 );
let zeroX = map(preStim, 0 , totalSamples, 50 , width - 20 );
line(zeroX, topY, zeroX, topY + panelH);
// Horizontal axis + tick marks
stroke( 0 , 0 , 60 );
line( 50 , topY + panelH, width - 20 , topY + panelH);
for ( let i = 0 ; i <= totalSamples; i += 25 ) {
let x = map(i, 0 , totalSamples, 50 , width - 20 );
stroke( 0 , 0 , 100 );
line(x, topY + panelH, x, topY + panelH + 15 );
noStroke();
fill( 0 , 0 , 100 );
textAlign( CENTER , TOP );
textSize( 12 );
text(round((i - preStim) / 256 * 1000 ), x, topY + panelH + 18 );
}
// Legend
fill(colors.face[ 0 ], colors.face[ 1 ], colors.face[ 2 ]);
noStroke();
rect(width - 150 , topY, 10 , 10 );
fill( 0 , 0 , 100 );
textAlign( LEFT , CENTER );
text( "Face" , width - 135 , topY + 5 );
fill(colors.object[ 0 ], colors.object[ 1 ], colors.object[ 2 ]);
noStroke();
rect(width - 150 , topY + 20 , 10 , 10 );
fill( 0 , 0 , 100 );
text( "Object" , width - 135 , topY + 25 );
// --- Bottom panel: Stimulus ---
let bottomY = height * 0.65 ;
fill( 0 , 0 , 100 );
textAlign( RIGHT , TOP );
textSize( 12 );
text( `Face Trials: ${faceTrials. TP9 .length} ` , width - 20 , bottomY - 60 );
text( `Object Trials: ${objectTrials. TP9 .length} ` , width - 20 , bottomY - 40 );
if (stimState === 1 ) {
if (showFace) {
drawFaceExpression(currentFace, width / 2 , bottomY, 70 ); // smaller faces
fill( 0 , 0 , 100 );
textAlign( CENTER );
textSize( 10 );
text(currentFace.toUpperCase(), width / 2 , bottomY + 40 );
} else {
fill( 0 , 0 , 100 );
noStroke();
let objSize = 55 ; // 🔹 increased from 40
if (currentObject === 'square' ) {
rect(width / 2 - objSize / 2 , bottomY - objSize / 2 , objSize, objSize);
} else if (currentObject === 'triangle' ) {
let triH = objSize * 1.1 ;
triangle(
width / 2 , bottomY - triH / 2 ,
width / 2 - objSize / 2 , bottomY + triH / 2 ,
width / 2 + objSize / 2 , bottomY + triH / 2
);
} else if (currentObject === 'star' ) {
drawStar(width / 2 , bottomY, objSize * 0.6 , objSize / 3 , 5 );
}
fill( 0 , 0 , 100 );
textAlign( CENTER );
textSize( 10 );
text(currentObject.toUpperCase(), width / 2 , bottomY + 40 );
}
}
fill( 0 , 0 , 100 );
textAlign( CENTER );
textSize( 12 );
text( "Trial " + trial, width / 2 , 20 );
}
// --- Draw a 5-pointed star ---
function drawStar(x, y, radius1, radius2, npoints) {
let angle = TWO_PI / npoints;
let halfAngle = angle / 2.0 ;
beginShape();
for ( let a = 0 ; a < TWO_PI ; a += angle) {
let sx = x + cos(a) * radius2;
let sy = y + sin(a) * radius2;
vertex(sx, sy);
sx = x + cos(a + halfAngle) * radius1;
sy = y + sin(a + halfAngle) * radius1;
vertex(sx, sy);
}
endShape( CLOSE );
}
// --- Smaller face expressions ---
function drawFaceExpression(type, x, y, size) {
noStroke();
fill( 0 , 0 , 100 );
ellipse(x, y, size, size * 1.1 );
fill( 20 );
let eyeW = size * 0.12 ;
let eyeH = size * 0.12 ;
ellipse(x - size * 0.2 , y - size * 0.18 , eyeW, eyeH);
ellipse(x + size * 0.2 , y - size * 0.18 , eyeW, eyeH);
stroke( 20 );
strokeWeight( 2 );
noFill();
if (type === 'happy' ) {
arc(x, y + size * 0.15 , size * 0.45 , size * 0.25 , 0 , PI );
} else if (type === 'sad' ) {
arc(x, y + size * 0.25 , size * 0.45 , size * 0.25 , PI , TWO_PI );
} else if (type === 'surprised' ) {
noStroke();
fill( 20 );
ellipse(x, y + size * 0.18 , size * 0.12 , size * 0.12 );
}
noStroke();
}
// --- Helper function: average trials with baseline correction ---
function averageEpoch(trials) {
if (!trials || trials.length === 0 ) return null ;
let avg = Array (trials[ 0 ].length).fill( 0 );
trials.forEach(trial => {
let baseline = 0 ;
for ( let i = 0 ; i < preStim; i++) baseline += trial[i];
baseline /= preStim;
for ( let i = 0 ; i < trial.length; i++) {
avg[i] += trial[i] - baseline;
}
});
for ( let i = 0 ; i < avg.length; i++) avg[i] /= trials.length;
return avg;
}
📝 Archibald_Midterm_Part1.txt - Caitlyn Archibald.txt
💡 Code is embedded in this portfolio - opens instantly in the live BrainImation editor (no internet required!)
📝 Archibald_Midterm_Part2 - Caitlyn Archibald.txt
💡 Code is embedded in this portfolio - opens instantly in the live BrainImation editor (no internet required!)