Functional Design
Concepts
Concept: SongifiedNote[RawNote]
Purpose: Facilitates learning by converting study notes into catchy, memorable songs.
Principle: Users provide study note and specify their desired song template; the notes are then transformed into lyrics set to a chosen background music template, resulting in an engaging and educational song that helps with memorization.
State:
author: SongifiedNote -> one User
rawNote: SongifiedNote -> one String
generatedLyrics: SongifiedNote -> one String
lyricTemplate: SongifiedNote -> one String
backgroundMusic: SongifiedNote -> one Audio
quizCard: SongifiedNote -> set String
author: SongifiedNote -> one User
rawNote: SongifiedNote -> one String
generatedLyrics: SongifiedNote -> one String
lyricTemplate: SongifiedNote -> one String
backgroundMusic: SongifiedNote -> one Audio
quizCard: SongifiedNote -> set String
Actions:
generateSongifiedNote(rawnote: String, songTemplate: SongTemplate, user: User):
// Generates a new SongifiedNote using a template and raw study notes.
editLyrics(newlyrics: String, songifiedNote: SongifiedNote):
//Edits the lyrics of an existing SongifiedNote.
deleteSongifiedNote():
//Removes a SongifiedNote instance
editNotes(newnotes: String):
// Edits the notes corresponding to the songified note
songsOwnedByUser(user: User):
// returns SongifiedNotes owned by user
deleteSongsOwnedByUser(user: User):
// deletes Songs Owned By User
isAuthor(user: User):
// returns true if user is author
get(id):
// returns songified note with the given id
generateQuizCard():
// Generates quiz card from a songified note.
// Quiz card has some random part of the lyrics missing, so that
// users can quiz themselves and input the missing part.
generateSongifiedNote(rawnote: String, songTemplate: SongTemplate, user: User):
// Generates a new SongifiedNote using a template and raw study notes.
editLyrics(newlyrics: String, songifiedNote: SongifiedNote):
//Edits the lyrics of an existing SongifiedNote.
deleteSongifiedNote():
//Removes a SongifiedNote instance
editNotes(newnotes: String):
// Edits the notes corresponding to the songified note
songsOwnedByUser(user: User):
// returns SongifiedNotes owned by user
deleteSongsOwnedByUser(user: User):
// deletes Songs Owned By User
isAuthor(user: User):
// returns true if user is author
get(id):
// returns songified note with the given id
generateQuizCard():
// Generates quiz card from a songified note.
// Quiz card has some random part of the lyrics missing, so that
// users can quiz themselves and input the missing part.
Concept: SongCollection[SongifiedNote]
Purpose: To organize and store a collection of SongifiedNotes, enabling users to access and manage their created song notes efficiently. Song collections can be viewed by users privately or shared with the people on the TuneTrainer.
Principle: This concept allows users to group their songified study notes into collections. Each collection can be created, modified, and deleted by the user. Users can add new SongifiedNotes to a collection, update existing notes, or remove single songifiedNote from a collection.
State
title: SongCollection -> one String
description: SongColleciton -> one String
songifiedNotes: SongCollection -> set SongifiedNote
upvotes: SongCollection -> one Integer
Owner: SongCollection -> one User
title: SongCollection -> one String
description: SongColleciton -> one String
songifiedNotes: SongCollection -> set SongifiedNote
upvotes: SongCollection -> one Integer
Owner: SongCollection -> one User
Actions:
create(out songCollection: SongCollection, name: String, user: User):
// Initialize a new SongCollection with a unique name and a user as the owner.
addSongifiedNote(note: SongifiedNote, songCollection: SongCollection):
// Add a new SongifiedNote to the SongCollection.
deleteNote(note: SongifiedNote, songCollection: SongCollection):
// Remove a single SongifiedNote from the SongCollection.
deleteNoteFromAllCollections(note: SongifiedNote):
// removes the SongifiedNote from all collections that it is a part of
update(name: String, desc: String):
// Update the name and/or description properties of the SongCollection
deleteCollection():
// Delete the entire SongCollection.
getOwner():
// Retrieve the information of the user who owns the SongCollection.
getCollectionsOwnedBy(user: User):
// list all collections owned by the user
upvoteCollection():
// upvote a SongCollection
downvoteCollection():
// downvote a SongCollection
getCOllectionStats():
// get the number of upvotes & downvotes of a SongCollection
create(out songCollection: SongCollection, name: String, user: User):
// Initialize a new SongCollection with a unique name and a user as the owner.
addSongifiedNote(note: SongifiedNote, songCollection: SongCollection):
// Add a new SongifiedNote to the SongCollection.
deleteNote(note: SongifiedNote, songCollection: SongCollection):
// Remove a single SongifiedNote from the SongCollection.
deleteNoteFromAllCollections(note: SongifiedNote):
// removes the SongifiedNote from all collections that it is a part of
update(name: String, desc: String):
// Update the name and/or description properties of the SongCollection
deleteCollection():
// Delete the entire SongCollection.
getOwner():
// Retrieve the information of the user who owns the SongCollection.
getCollectionsOwnedBy(user: User):
// list all collections owned by the user
upvoteCollection():
// upvote a SongCollection
downvoteCollection():
// downvote a SongCollection
getCOllectionStats():
// get the number of upvotes & downvotes of a SongCollection
Concept: StudyTool [SongCollection]
Purpose: To provide an interactive and adaptive learning experience. Users will be able to study all the songifiedNotes in their SongCollection. Studying will be done using spaced repetition to reinforce memory retention and recall.
Principle: The StudyTool dynamically adjusts the presentation of songifiedNotes based on user interactions. It assesses user familiarity with each note and employs spaced repetition algorithms to prioritize notes needing more practice. This personalized approach aims to maximize learning efficiency and retention.
State:
notesDisplayQueue: one Collection -> set SongifiedNote
priorityScore: one SongifiedNote -> one Integer
notesDisplayQueue: one Collection -> set SongifiedNote
priorityScore: one SongifiedNote -> one Integer
Actions:
initializeStudySession(collection: SongCollection):
// Sets up a new study session. Priority scores of the songified notes are all the same.
nextSongifiedNote(que: notesDisplayQueue):
// Retrieves the next songifiedNote from the notesDisplayQueue based on the highest priority score and presents it to the user for study
updatePriorityScore(note: SongifiedNote, userResponse: Boolean):
//Adjusts the priorityScore for the specified songifiedNote. If the user response indicates successful recall (userResponse is true), the score is decreased. If the user struggles with the note (userResponse is false), the score is increased.
reorderQueue(que: notesDisplayQueue):
// Sorts the notesDisplayQueue so that notes with higher priority scores are presented more frequently, adhering to the principles of spaced repetition.
resetQueue(que: notesDisplayQueue):
// Completely reset the queue. Make priority scores of the songified notes be all the same.
resumeStudySession(collection: SongCollection):
// This is different from init session. When you start session that means you have already studied with the tool, hence your notesDisplayQueue is configured.
initializeStudySession(collection: SongCollection):
// Sets up a new study session. Priority scores of the songified notes are all the same.
nextSongifiedNote(que: notesDisplayQueue):
// Retrieves the next songifiedNote from the notesDisplayQueue based on the highest priority score and presents it to the user for study
updatePriorityScore(note: SongifiedNote, userResponse: Boolean):
//Adjusts the priorityScore for the specified songifiedNote. If the user response indicates successful recall (userResponse is true), the score is decreased. If the user struggles with the note (userResponse is false), the score is increased.
reorderQueue(que: notesDisplayQueue):
// Sorts the notesDisplayQueue so that notes with higher priority scores are presented more frequently, adhering to the principles of spaced repetition.
resetQueue(que: notesDisplayQueue):
// Completely reset the queue. Make priority scores of the songified notes be all the same.
resumeStudySession(collection: SongCollection):
// This is different from init session. When you start session that means you have already studied with the tool, hence your notesDisplayQueue is configured.
Concept: Collaboration[SongCollection]
Purpose: To enable collaborative creation and management of SongCollections. This concept allows users to work together on SongCollections, contributing and removing SongifiedNotes for shared educational purposes.
Principle: Allows multiple users to contribute to a single SongCollection. Collection owner has the authority to approve or reject proposed SongifiedNotes. Contributors can suggest additions but final decisions rest with the owner, ensuring a coherent and targeted collection aligned with the owner's vision.
State:
contributors: SongCollection -> set User
proposedAdditions: SongCollection -> set SongifiedNote
approvalStatus: SongifiedNote -> one of {Pending, Approved, Rejected}
contributors: SongCollection -> set User
proposedAdditions: SongCollection -> set SongifiedNote
approvalStatus: SongifiedNote -> one of {Pending, Approved, Rejected}
Actions:
createCollaborativeCollection(collection: SongCollection):
// Initializes a new Collaborative SongCollection from already existing SongCollection
addContributor(user: User):
// Allows the owner to add a new contributor to the SongCollection.
proposeAddition(contributor: User, note: SongifiedNote):
// A contributor proposes a new SongifiedNote to be added to the collection. The note's status is set to Pending.
getCollectionsOwnedByUser(user: User):
// returns the collections that the user owns
getCollaborations(user: User)
// returns the collections that the user can contribute to; if a collection is deleted, it should not show up in the output
getPendingSuggestions(collection: SongCollection):
// returns all SongifiedNotes with a Pending status for this collection
deleteRecords(collection: SongCollection)
// deletes the records corresponding to `collection`
ownerApproveChange(owner: User, note: SongifiedNote):
// The owner approves a proposed addition or removal of a SongifiedNote, changing its status to Approved.
ownerRejectChange(owner: User, note: SongifiedNote):
// The owner rejects a proposed addition or removal of a SongifiedNote, changing its status to Rejected.
markAsOwner(collection: SongCollection, user: User):
// marks the Owner as the song collection owner
createCollaborativeCollection(collection: SongCollection):
// Initializes a new Collaborative SongCollection from already existing SongCollection
addContributor(user: User):
// Allows the owner to add a new contributor to the SongCollection.
proposeAddition(contributor: User, note: SongifiedNote):
// A contributor proposes a new SongifiedNote to be added to the collection. The note's status is set to Pending.
getCollectionsOwnedByUser(user: User):
// returns the collections that the user owns
getCollaborations(user: User)
// returns the collections that the user can contribute to; if a collection is deleted, it should not show up in the output
getPendingSuggestions(collection: SongCollection):
// returns all SongifiedNotes with a Pending status for this collection
deleteRecords(collection: SongCollection)
// deletes the records corresponding to `collection`
ownerApproveChange(owner: User, note: SongifiedNote):
// The owner approves a proposed addition or removal of a SongifiedNote, changing its status to Approved.
ownerRejectChange(owner: User, note: SongifiedNote):
// The owner rejects a proposed addition or removal of a SongifiedNote, changing its status to Rejected.
markAsOwner(collection: SongCollection, user: User):
// marks the Owner as the song collection owner
Concept: Share[SongCollection]
Purpose: Enhances user engagement by allowing users to share their SongCollections with different levels of accessibility: private, restricted to specific users, or public. This feature promotes community interaction and discovery of diverse study materials.
Principle: Users can determine the visibility of their SongCollections. Options include keeping it private (personal use), sharing with select users (collaboration), or making it public (community contribution). Public collections appear on an 'Explore Collections' page, fostering a community learning environment. The sharing settings can be modified at any time, providing flexibility and control.
State:
visibilityStatus: SongCollection -> one of {Private, Restricted, Public}
sharedWith: SongCollection -> set User
//all collections that are public (displayed on explore page)
exploreCollections: set SongCollection
visibilityStatus: SongCollection -> one of {Private, Restricted, Public}
sharedWith: SongCollection -> set User
//all collections that are public (displayed on explore page)
exploreCollections: set SongCollection
Actions
setPrivate(collection: SongCollection):
// Sets a SongCollection's visibility to Private. Only the owner can view or edit it.
setRestricted(collection: SongCollection, users: set User):
// Sets a SongCollection's visibility to Restricted and specifies the users who can access it.
setPublic(collection: SongCollection):
// Sets a SongCollection's visibility to Public, making it accessible to all app users and featured on the 'Explore Collections' page.
userHasAccess(user: User, song: SongifiedNote):
// returns true if the user has access to the songified note
modifySharedUsers(collection: SongCollection, users: set User):
// Modifies the list of users who have access to a Restricted SongCollection.
addToExploreCollections(collection: SongCollection):
// Adds a Public SongCollection to the 'Explore Collections' page for community discovery.
getUsersWithAccessToColection(user: User, collection: SongCollection):
// returns the list of users with access to the collection
getCollectionsAccessibleByUser(user: User):
// returns the list of SongCollections accessible to the user
removeFromExploreCollections(collection: SongCollection):
// Removes a SongCollection from the 'Explore Collections' page, typically when its visibility status changes.
updateVisibilityStatus(collection: SongCollection, status: one of {Private, Restricted, Public}):
// Updates the visibility status of a SongCollection, triggering necessary actions like adding to or removing from the 'Explore Collections' page.
setPrivate(collection: SongCollection):
// Sets a SongCollection's visibility to Private. Only the owner can view or edit it.
setRestricted(collection: SongCollection, users: set User):
// Sets a SongCollection's visibility to Restricted and specifies the users who can access it.
setPublic(collection: SongCollection):
// Sets a SongCollection's visibility to Public, making it accessible to all app users and featured on the 'Explore Collections' page.
userHasAccess(user: User, song: SongifiedNote):
// returns true if the user has access to the songified note
modifySharedUsers(collection: SongCollection, users: set User):
// Modifies the list of users who have access to a Restricted SongCollection.
addToExploreCollections(collection: SongCollection):
// Adds a Public SongCollection to the 'Explore Collections' page for community discovery.
getUsersWithAccessToColection(user: User, collection: SongCollection):
// returns the list of users with access to the collection
getCollectionsAccessibleByUser(user: User):
// returns the list of SongCollections accessible to the user
removeFromExploreCollections(collection: SongCollection):
// Removes a SongCollection from the 'Explore Collections' page, typically when its visibility status changes.
updateVisibilityStatus(collection: SongCollection, status: one of {Private, Restricted, Public}):
// Updates the visibility status of a SongCollection, triggering necessary actions like adding to or removing from the 'Explore Collections' page.
Synchronizations
app TuneTrainer
include SongifiedNote[RawNote]
include SongCollection[SongifiedNote]
include StudyTool[SongCollection]
include Collaboration[SongCollection]
include Share[SongCollection]
sync getNotesAccessibleToUser(user: User):
songs = Share.getCollectionsSharedWithUser(user).map(collection =>
SongCollection.getSongs(collections));
songs += Songs.getSongsOwnedBy(user);
return songs
sync deleteSongifiedNote(songNote: SongifiedNote, user: User)
if SongifiedNote.isAuthor(user, songNote):
when SongifiedNote.delete(songNote):
for (songCollection of SongCollection.collections) {
SongCollection.deleteNote(songNote, songCollection);
}
sync getSongifiedNote(songifiedNoteId: ObjectId, user: User):
If (songifiedNoteId in getNotesAccessibleToUser(user)):
return SongifiedNote.get(songifiedNoteId);
sync approveSongAddition(user: User, songNote: SongifiedNote, collection: SongCollection):
when Collaboration.ownerApproveChange(user, songNote, collection);
SongCollection.addSongifiedNote(songNote, collection);
sync createSongCollection(name: String, user: User)
when collection = SongCollection.create(name, user):
Share.setPrivate(collection);
Collaboration.markAsOwner(user);
Share.addNewUsers(user);
sync deleteSongCollection(collection: SongCollection, user: User)
if SongCollection.getOwner(collection) == user:
when SongCollection.delete(collection):
Share.deleteRecords(collection);
Collaboration.deleteRecords(collection);
sync deleteUser(user: User):
when User.delete(user):
SongifiedNote.getSongsOwnedBy(user).map(song => deleteSongifiedNote(song, user) );
SongCollection.getCollectionsOwnedBy(user).map(collection => deleteSongCollection(collection, user));
app TuneTrainer
include SongifiedNote[RawNote]
include SongCollection[SongifiedNote]
include StudyTool[SongCollection]
include Collaboration[SongCollection]
include Share[SongCollection]
sync getNotesAccessibleToUser(user: User):
songs = Share.getCollectionsSharedWithUser(user).map(collection =>
SongCollection.getSongs(collections));
songs += Songs.getSongsOwnedBy(user);
return songs
sync deleteSongifiedNote(songNote: SongifiedNote, user: User)
if SongifiedNote.isAuthor(user, songNote):
when SongifiedNote.delete(songNote):
for (songCollection of SongCollection.collections) {
SongCollection.deleteNote(songNote, songCollection);
}
sync getSongifiedNote(songifiedNoteId: ObjectId, user: User):
If (songifiedNoteId in getNotesAccessibleToUser(user)):
return SongifiedNote.get(songifiedNoteId);
sync approveSongAddition(user: User, songNote: SongifiedNote, collection: SongCollection):
when Collaboration.ownerApproveChange(user, songNote, collection);
SongCollection.addSongifiedNote(songNote, collection);
sync createSongCollection(name: String, user: User)
when collection = SongCollection.create(name, user):
Share.setPrivate(collection);
Collaboration.markAsOwner(user);
Share.addNewUsers(user);
sync deleteSongCollection(collection: SongCollection, user: User)
if SongCollection.getOwner(collection) == user:
when SongCollection.delete(collection):
Share.deleteRecords(collection);
Collaboration.deleteRecords(collection);
sync deleteUser(user: User):
when User.delete(user):
SongifiedNote.getSongsOwnedBy(user).map(song => deleteSongifiedNote(song, user) );
SongCollection.getCollectionsOwnedBy(user).map(collection => deleteSongCollection(collection, user));
Dependency Diagram
Wireframes
Here's the figma link: https://www.figma.com/file/bBolyPxP3ezJGn5UHWvDec/TuneTrainer-Wireframes?type=design&node-id=0%3A1&mode=design&t=TNcw67Yx3wrzxAzD-1
Heuristic Evaluation
Usability:
Learnability: The navigation bar simplifies user interaction by offering easy access to existing collections, song generation, viewing others' collections, and accessing the study tool. This straightforward layout enhances learnability by providing direct and immediate paths to key functionalities without extensive exploration or learning. However, ensuring consistent labeling across these navigation elements can further improve clarity and ease of use. Employing uniform text labels alongside the icons could enhance learnability for users who rely more on textual cues while maintaining intuitive navigation.
Efficiency: Some screens involve excessive scrolling due to the clustering of information, potentially impeding efficiency. Simplifying content or introducing collapsible songs within a collection could streamline the user journey.
Linguistic Level:
Speak a User's Language: The wireframes consistently utilize concise and instructive messages like "Play Song," "Study," or "Songify This Note" for guiding user actions. These clear directives aid user comprehension by succinctly conveying what actions are available or what they do. This simplicity in messaging contributes to a user-friendly interface, helping users quickly grasp available functionalities. Enhancing simplicity with intuitive visual cues, like color variations or subtle animations, can reinforce the meaning behind simple messages. Employing these visual cues can aid user understanding without adding textual complexity, creating a more engaging interface experience.
Consistency: The wireframes maintain robust consistency by employing identical names, symbols, and icons consistently across the interface for distinct concepts and actions. For instance, the "14 songs" label consistently denotes the number of songs within each collection. Additionally, every collection title is bolded to signify it’s a collection. Meanwhile, individual songs are consistently represented by a music symbol icon along with the name of the song the notes are created from, distinguishing them clearly within the interface. As for improvement, implementing a standardized naming convention for collections and songs across the interface could further reinforce consistency. For instance, ensuring that collection titles consistently follow a specific format or structure (such as "Album - Title" or "Playlist: Title") and that song names consistently reflect their source or genre could bolster uniformity throughout the application.
Physical:
Fitt’s Law: The "Add Collection" and "Generate Song" buttons' significant size adheres to Fitt's Law, allowing users to swiftly convert their notes into songs. Their prominent size and placement facilitate quick access, aligning with Fitt's Law principles by reducing the time and effort required for this essential action. However, while the size is advantageous, it's also crucial to ensure the spacing and arrangement of these buttons for optimal ergonomic interaction, preventing accidental touches while maintaining swift access. Adjusting their proximity could further enhance user experience without compromising their accessibility.
Accelerators: Currently, the interface lacks shortcuts to expedite actions for experienced users. Incorporating quick shortcut buttons or hotkeys could significantly enhance the efficiency of expert users navigating the application. By introducing easily accessible shortcuts for frequently used functionalities, such as creating collections or generating songs, the interface could offer a more streamlined experience, reducing the number of steps needed to perform common tasks. Implementing these quick access buttons aligns with the goal of aiding proficient users in swiftly accomplishing their objectives within the app.
Tradeoffs
Tradeoff between Safety and Efficiency:
In the wireframes, the omission of extensive confirmation dialogs or multiple-step verifications before critical actions supports efficiency but might compromise safety to some extent. For example, when a user initiates the generation of a song from notes or the deletion of a collection, there's a direct action without multiple prompts for confirmation. While this streamlined process promotes efficiency, allowing users to swiftly proceed with their intended actions, it poses a potential safety risk. Lack of confirmation dialogs or additional steps could lead to accidental deletions or irreversible actions, potentially compromising the safety of user data or critical content. Balancing safety and efficiency involves a delicate tradeoff. Implementing more confirmation steps might enhance safety by preventing accidental actions, but it might slightly hinder efficiency by adding extra steps. Striking the right balance between these aspects is crucial to ensure both a secure environment and a streamlined user experience.
Tradeoff between Fitts's Law and Gestalt Principles in Layout:
The wireframes prioritize Fitts's Law by enlarging and emphasizing interactive elements like “add Song/Collection” buttons, adhering to the principle of ease of access and interaction. However, this emphasis on larger elements for better accessibility might conflict with some Gestalt principles related to the layout's conveyance of conceptual structure. While larger buttons enhance usability and ease of interaction, they might impact the overall visual hierarchy and grouping based on Gestalt principles like proximity and similarity. Enlarging certain elements could disrupt the spatial relationships and balance among interface components, potentially affecting the overall conceptual structure and clarity of the layout. Optimizing for Fitts's Law by increasing the size of interactive elements might inadvertently challenge the Gestalt principles' consistency in grouping related elements or maintaining balanced spatial relationships within the interface. Finding a balance between accessibility and preserving the visual harmony necessary for conveying conceptual structure is crucial for an effective interface design.
Visual Design Study
Google Slides:
https://docs.google.com/presentation/d/1sdsi35sMTZSs-LhrSoCy8kAkvCCCGvYT6FGhgmCYz-0/edit?usp=sharing
Implementation Plan
Here is the Google Doc with Implementation Plan:
https://docs.google.com/document/d/1rMFlc5wxKe2Rato4WsC5mN5VHUhi6-ZHKE-YTE1_9hI/edit?usp=sharing