web designer and developer

Lüper

January - May 2013

Lüper is a simple audio recording, editing and looping app modeled after the guitar loop pedal for Android devices. Developed in Java using the Android SDK.

I worked in a team of 10 to learn and develop this application for Android devices. My main responsibilities were: working on the design and UI of the application, developing on the project editor interface (the interface where users interact with elements to add/modify tracks, pause/play current project, and drag playhead), writing the user maual and administrator guide, and developing the app's landing page website.

The goal of the application is two fold: make music production accessible to the casual smartphone user, and allow aspiring music artists the ability to share their musical creations with other Lüper users and external social media outlets.

Once a user downloads the Lüper app, he/she can choose to create a new project or open a previously saved one. Once in a project, the user can record or import audio, manage clips and tracks, create loops, and apply filters. When done, the user can save their project for future editing, or upload their finished sequence to the Lüper server. The server relays information about other users’ creations back to the user, so he/she can use them as inspiration for new sequences or edit them further to create his/her own remixes.

- GitHub Repository
- System Design Specifications
- System Requirements Specifications
- Release Notes




Use Case:

Luper

System Architecture:

  • The system is constructed around four major components: the activity-based graphical user interface, the audio files and their metadata, the project editor, and the local and remote databases.
  • The Project Editor is the toolset for modifying Lüper projects. It displays audio sequences on-screen, implements simple editing functionality, and lets the user export projects as compressed audio files.
  • Activities encapsulate job-specific features and their associated graphical components (e.g. buttons, previews, lists).
  • Lüper stores its data in two databases: one on the phone and one on the server. Both databases store the same data, with the master version on the server and a subset (generally only the rows the user “needs”) on the phone. The data is automatically synchronized between the phone and the server by both real-time and periodic updates, whichever applies better to a given piece of data.

Luper

Design View/UI Flow:

  • Login: Asks for email and password inputs from user requiring the sign-in button to be clicked for any request to be generated. User can log in using a Luper account or a Facebook account. There will also be a Register link (for Luper account).
  • Register: Asks for an e-mail address, screen name and a password. To process the request, the Register button has to be clicked.
  • Home Tab: The Home Tab has a welcome screen and Tutorial button. The button links to a quick tutorial panel.
    • Tutorial: The Tutorial link on the start panel will refer the user to a tutorial page consisting of a description of how to use the application.
  • Friends Tab: The Friends Tab has a list of friends the user has.
  • Projects Tab: The Projects Tab contains a list of saved projects. The user can open one of them. If the user uses the New Project button, the project is automatically added to the list.
  • New Project: New Project button is available from any place of the application after the user is logged in. It opens up a New Project panel where the user can choose name for the project. After the project is created, it is added to the list in the Projects Tab.
  • Open Project: If the user clicks on the project title in the Projects Tab, it opens the selected project in the Project Editor.
  • Project Editor: At the top of the Project Editor panel there are buttons to: add new track, play, change volume, export, and share. Tracks and clips are displayed on the main screen and can be clicked to be edited.
    • Add Track: Clicking the Add Track will create a new track and place it on the timeline. From each track the user can add a clip to the track (record/browse) or play only this track.
    • Play: Clicking Play button plays the whole project (all tracks at the same time).
    • Volume: Volume button allows to regulate the volume of the whole project.

Luper

Login Process:

  • The Login process begins at the Login panel rendered by Login Activity. Users with existing profiles only have to provide their email and password to access their projects. The process for successful login begins when the user fills out the Email field. The Android keyboard API processes input, and passes text to the Login Activity.
  • Next the user supplies the correct password. Again, the Android Keyboard API processes input, however, the password isn’t displayed in plaintext. Each character is obscured.
  • Once the email and password are entered, the user selects the login button. The user data is authenticated against the database. If email or password are incorrect, user gets an error message. Upon successful login the app switches to the Home Tab.

Luper

Record & Add Track:

  • To record and insert a new clip the user first selects a track to record to. The user selects the recordClip function to begin recording. Once the user decides to stop the recording, the new clip is inserted into the track.

Luper

Creating New Projects:

  • App user creates a new project. Then by pressing New Track button user creates one or more tracks. For each track user can record or load several clips. After user is done, they press Save Project, which saves the project.

Design Patterns & Principles Used:

  • TabsAdapter.java is an example of Adapter design pattern.It implements the management of tabs and all details of connecting a ViewPager with associated TabHost. Normally a tab host has a simple API for supplying a View or Intent that each tab will show. This is not sufficient for switching between pages. So instead we make the content part of the tab host 0dp high (it is not shown) and the TabsAdapter supplies its own dummy view to show as the tab content. It listens to changes in tabs, and takes care of switch to the correct paged in the ViewPager whenever the selected tab changes.
  • DialogFactory.java is an example of Factory design pattern. It contains helper methods to make it easier to include a popup message in either dialog or toast form.
  • SQLiteDataSource.java class creates data objects (User, Sequence, Track, Clip, and AudioFile). The createX methods SQLiteDataSource are called instead of constructors of data objects. It is another example of Factory pattern.

Databases

  • MySQL: Team Luper has provisioned a dedicated Amazon S3 instance which hosts the teamluper.com website and a REST API service for accessing the data in its MySQL database. The REST API is implemented with Slim (http://slimframework.com/), a PHP micro-framework for creating basic HTTP services, and uses PHP’s Data Objects interface (PDO) for secure and access to MySQL from within Slim. Slim’s flexibility and small size makes it ideal for this use, and PDO’s implementation is more efficient and safer against SQL injection attacks than PHP’s classic php_mysql extension. The decision to use PHP and Slim is made out of personal preference and familiarity (this same structure could be implemented just as well with Python or Ruby, etc), but the PDO method of accessing MySQL is recognized to be the “best way” in modern PHP.
  • SQLite: While MySQL is a high-performance server-class database management system (DBMS), it is too bulky to be an optimal choice for storing data on the Android device. The low-overhead SQLite is a fitting choice, and since both SQLite and MySQL share the SQL language and data structures, they can be used to store the same data without the need for awkward conversion routines. SQLite also has the convenient feature of storing its tables as flat files, which can easily be backed up and restored, or placed on external storage such as a MicroSD card. SQLite is widely used for mobile applications and its usage on Android is well documented.
  • An Abstract Request Method for Both Data Sources
    The goal of this implementation is to always have data access be as responsive as possible while keeping the data as up-to-date as possible. Each time the Android application requests some data from its database component, the request method first checks to see if the requested data is available locally in SQLite. If so, the method returns the data, immediately resuming it’s caller’s control flow (this is the normal pattern for data access while the device is offline). If the time since the returned data was last updated is above an arbitrary “freshness” threshold and the device is currently connected to the internet, a background task is launched to check for a newer version of the same data by running the same query on the server and comparing it with the results returned from SQLite (this is possible without having to actually download the data, using hashes). If the MySQL data is found to be different from the SQLite data, a sync/refresh is triggered in the background. If the data was never available in SQLite to begin with, a sync/refresh is triggered in the foreground, blocking the user with a loading message until it is complete, upon which the request method will complete and resume its caller’s control flow. Unifying the access this way provides a single abstract method for requesting the data without regard to whether you are querying MySQL or SQLite (you just get a delay before the method returns when the data came from MySQL, where you don’t when it came from SQLite).
  • The Sync/Refresh Routine
    The sync/refresh routine itself simply runs a query on the server for all of the “relevant data” as defined above, then downloads the data to the device. When the download is complete, the data is merged into the SQLite database. The resulting merged SQLite data is then compared with the downloaded MySQL data to see if there were any new local changes that need to be synced back up to the server. If the two are the same after the merge, everything is in sync and the routine is complete. If not, an UPDATE query is assembled from all the SQLite data which differed from the MySQL data in the comparison, and then uploaded along with the hash of the MySQL data it was based on. When this upload is complete, if the hash matches the hash from the current version of the server’s MySQL database, the UPDATE is executed, everything is in sync and the routine is complete. If the hashes don’t match, something on the server changed during the upload and the sync/refresh routine is restarted from the beginning. Technically, if other users are uploading changes faster than you can download them, this could result in a never-ending series of refresh, check, refresh, check... so there should be a sanity check to prevent this. However, this consideration is only a factor if we want to implement real-time collaboration, where someone else can change the data you’re trying to access.

Bug Tracking:

  • Github is used to track bugs, issues, and missing features. Github allows developers to mark issues with priority labels, the highest of which require immediate attention. The issues can be assigned to team members and commented on. Since every developer has a list of issues assigned to them, issues are constantly being fixed. Team members will receive notifications in their inbox concerning issues and whatever progress is made in dealing with them.

Testing:

  • Unit Testing: Test the methods of SQLiteDataSource that interact with the database. Some methods create data objects and put corresponding entries in the database. For those we check that the object was successfully created and has correct values. Other methods fetch entries from the database. For these we check that the fetched objects were the ones we were looking for. Finally, there are delete methods, for which we check that objects are properly deleted from the database.
  • System Testing: Test the interactions of data classes with SQLiteDataSource class through the database. The setter methods of data classes should update the corresponding entries in the database, which is managed by SQLiteDataSource.