Inheritance
Inheritance is a key concept in object-oriented programming that allows one class to derive or “inherit” properties and methods from another class. This promotes abstraction, enabling developers to model complex systems by focusing on shared characteristics. Combined with polymorphism, inheritance lets you extend or override behavior in subclasses, making your code more flexible and reusable.
In the example below, the NoteCell class inherits from the SoundCell class—originally implemented in the Classes chapter—adding its own unique property (color) and overriding the play and display methods. This approach is further explored in the Celtic handpan demo, showcasing how subclasses can build upon and customize base class functionality while retaining shared behavior.
(mouse press plays the note sound; key press picks a random note sound)
code
// Destructure array assignment into cellLength, outline, and outlineWeight
const [ cellLength, outline, outlineWeight ] = [400, 'red', 3];
// Initialize an array to store NoteCell instances
let notes = [];
let pickedNote;
// Base class representing a generic sound object
class SoundCell {
constructor(name, sound) {
this.setName(name); // Name of the sound object
this.sound = sound; // Associated sound file
}
// Set the name of the sound
setName(name) {
this.name = name;
}
// Retrieve the name of the sound object
getName() {
return this.name;
}
// Play the associated sound file
play() {
this.sound.play();
}
// Stop the sound file if it is playing
stop() {
this.sound.stop();
}
// Toggle sound playback (play if stopped, stop if playing)
toggle() {
this.sound.isPlaying() ? this.sound.stop() : this.sound.play();
}
// Display a visual representation of the sound object
display() {
push();
noFill();
stroke(outline);
strokeWeight(outlineWeight);
ellipseMode(CORNER);
circle(0, 0, cellLength); // Draw a circle with the given dimensions
pop();
}
}
// Subclass representing a note with additional visual properties
class NoteCell extends SoundCell {
constructor(note, sound, color) {
super(note, sound); // Initialize base class properties
this.color = color; // Color of the note
this.alpha = 0; // Transparency level for visual effects
}
// Override the play method to include visual feedback
play() {
super.play(); // Call the parent class's play method
this.alpha = 255; // Set alpha to maximum for the fade-out effect
}
// Override the display method to include color and manual fade-out effect
display() {
push();
// Reduce alpha manually each frame for a smooth fade-out effect
this.alpha = max(0, this.alpha - 5); // Fade from 255 to 0 over time
const hue = color(this.color);
hue.setAlpha(this.alpha); // Apply alpha transparency to the color
noStroke();
fill(hue);
ellipseMode(CORNER);
circle(0, 0, cellLength); // Draw the note with its color and alpha effect
// Call the parent class's display method
super.display();
pop();
}
}
// Load sound files and create NoteCell objects
async function setup() {
createCanvas(400, 400); // Setup the canvas
// Note names
const names = ['A3', 'A4', 'B2', 'B3', 'B4',
'CSharp4', 'D4', 'E4', 'FSharp3', 'FSharp4'];
// Corresponding colors
const colors = ['red', 'orange', 'yellow', 'lime', 'green',
'aqua', 'cyan', 'skyblue', 'blue', 'purple'];
for (let i = 0; i < names.length; i++) {
// Load the sound file for the note
const sound = await loadSound('/sounds/' + names[i] + '.wav');
const cellColor = colors[i]; // Get the color by index
// Create and add a NoteCell instance
notes.push(new NoteCell(names[i], sound, cellColor));
}
pickedNote = random(notes); // Select a random NoteCell from the notes array
}
// Render the currently picked note on the canvas
function draw() {
background(0); // Clear the canvas with a black background
pickedNote.display(width, 'red', 2); // Display the picked note
}
// Handle mouse press to play the currently picked note
function mousePressed() {
pickedNote.play(); // Play the sound associated with the picked note
}
// Handle key press to select a new random note
function keyPressed() {
pickedNote = random(notes); // Pick a new random note
}Class Definition
Inheritance in JavaScript is implemented using the extends keyword, which creates a parent-child relationship between two classes. In this example:
SoundCell: The base class, defining shared properties (name,sound) and methods (play,stop,toggle, anddisplay).NoteCell: The subclass, which extendsSoundCellby adding thecolorproperty and overriding theplayanddisplaymethods.
The following class diagram highlights the relationship between SoundCell and NoteCell:
classDiagram
direction BT
class SoundCell {
+String name
+p5.SoundFile sound
+setName(name: String)
+String getName()
+play()
+stop()
+toggle()
+display()
}
class NoteCell {
+String color
+Number alpha
+play()
+display()
}
NoteCell --|> SoundCell
Constructor and Object Instantiation
The SoundCell class includes a constructor that initializes the name and sound properties. This ensures that each instance of SoundCell or its subclasses has these properties defined.
class SoundCell {
constructor(name, sound) {
this.setName(name); // Name of the sound object
this.sound = sound; // Associate the sound file with this object
}
// Other methods omitted for brevity
}The NoteCell subclass extends this functionality by introducing a color property and an alpha property for visual feedback. Its constructor uses super to call the SoundCell constructor, ensuring that the shared properties (name and sound) are properly initialized before adding its unique color property.
class NoteCell extends SoundCell {
constructor(note, sound, color) {
super(note, sound); // Call parent constructor to initialize name and sound
this.color = color; // Assign the color property for this NoteCell
this.alpha = 0; // Initialize alpha property for fade-out effect
}
// Other methods omitted for brevity
}The setup function demonstrates how NoteCell objects are instantiated. It loads a list of note sounds and associates each with a corresponding color, creating a distinct NoteCell for each one. These objects are then stored in the global notes array for later use in the sketch.
async function setup() {
createCanvas(400, 400); // Setup the canvas
// Array of note names
const names = ['A3', 'A4', 'B2', 'B3', 'B4',
'CSharp4', 'D4', 'E4', 'FSharp3', 'FSharp4'];
// Array of corresponding colors
const colors = ['red', 'orange', 'yellow', 'lime', 'green',
'aqua', 'cyan', 'skyblue', 'blue', 'purple'];
for (let i = 0; i < names.length; i++) {
// Load the sound file for the current note
const sound = await loadSound('/sounds/' + names[i] + '.wav');
// Create and store a NoteCell instance
notes.push(new NoteCell(names[i], sound, colors[i]));
}
// Select a random NoteCell instance for display and interaction
pickedNote = random(notes);
}This approach demonstrates how inheritance enables the creation of multiple NoteCell instances that share common functionality from the SoundCell class while adding unique features like color and fade-out visual feedback.
Method Overriding and Polymorphism
The NoteCell class demonstrates method overriding, allowing it to redefine behavior from the parent class:
- The
playmethod is extended to include visual feedback by updating thealphaproperty. - The
displaymethod is customized to incorporate thecolorandalphaproperties, creating a fade-out effect as the sound plays.
These overrides enable polymorphism, allowing all SoundCell objects (including NoteCell instances) to be treated uniformly while invoking subclass-specific behavior at runtime.
Drawing
The draw function leverages the display method to visually represent the pickedNote instance on the canvas.
As with classes, it calls pickedNote.display, which dynamically updates the canvas based on the current state of the pickedNote object.
Here, the overridden display method in NoteCell adds color and transparency effects to the visual representation, creating a dynamic and interactive experience.
Interaction
Interaction remains similar to the implementation in classes, with mouse and keyboard events driving user input:
- Mouse Interaction: Clicking the mouse triggers the
playmethod, which starts the sound and sets the visual feedback (alpha). - Keyboard Interaction: Pressing any key selects a new random note from the
notesarray, allowing seamless interaction across multipleNoteCellinstances.
This showcases polymorphism by invoking methods dynamically on the current pickedNote object, regardless of whether it’s a SoundCell or a NoteCell.
Further Exploration
- Add a Toggle to Display Name: Add a feature to toggle the visibility of the name display, similar to how it’s implemented in the object literals and classes posts. This helps practice interacting with inherited methods and managing visibility of the name display dynamically.
- Implement Additional Subclasses: Create new subclasses that inherit from
SoundCellorNoteCell, each with unique properties and behaviors (e.g.,VolumeCellto adjust volume dynamically). - Explore Method Overloading: Extend the
playmethod inNoteCellto optionally take parameters (e.g.,play(loop = false)), showcasing method overloading and runtime flexibility. - Experiment with Visual Effects: Modify the
displaymethod inNoteCellto include animations or visual feedback based on the sound amplitude or playback speed.
References
p5 API
- createCanvas — Creates a drawing canvas on which all the 2D and 3D visuals are rendered in p5.js.
- push — Saves the current drawing state (e.g., styles, transformations) to the stack.
- pop — Restores the most recently saved drawing state from the stack.
- map — Re-maps a number from one range to another, often used to scale values for visual or interactive elements.
- noFill — Disables filling geometry shapes with color.
- fill — Sets the fill color for shapes drawn after this function is called.
- stroke — Sets the outline (stroke) color for shapes drawn after this function is called.
- strokeWeight — Sets the width of the stroke used for lines and shape outlines.
- ellipseMode — Changes the way the parameters of ellipse shapes are interpreted (e.g., center, corner).
- circle — Draws a circle at a specified location and diameter.
- loadSound — Loads an external sound file for playback in the sketch.
Further Reading
- JavaScript Inheritance — Learn how inheritance works in JavaScript.
- p5.js Sound Library — Official documentation for the sound library.