By Henning Vestergaard – henve12

1. Design & Idea Phase – Portfolio 1

1.1 Initial Idea

The project started with an idea revolving around a “Tone Matrix” which is an old web based flash application where a user can quickly make a sequence of tones play through and easy to use matrix-like interface. Each Cell in the matrix represents a tone, and the tones will be played in succesion as it plays each column of the matrix sequentially. The Tone Matrix is a very intuitive way for a naive user to work with sound, and requires no explanation to understand. Tone Matrix is a hands on, fun and relaxing experience, alike to an open-ended game experience.

Audiotools original Tone Matrix

1.2 Resarch & Testing

Trying to work further with this idea, research was done whether application alike to this existed for the iOS platform allready. This was done to find ideas for additional features, and maybe find faults in the existing applications that could be avoided further down the line.
There existed many of these Tone Matrix clones, and most of them had additional features such as saving your matrix, and changing values such as tempo or pitch as seen in SoundMatrix II by John Holdsworth. These features were interesting and worth pursuing when generating further ideas.
Another idea stemmed from working with AudioKit in our classes, as AudioKit has alot of possibilities when it comes to modifying sound output. With AudioKit it could be possible to add additional instruments that would work in tandem with the Tone Matrix, creating possibly a better experience.
Also it’s noteworthy that most of these applications lacked a play/pause button, which i personally believed to be a good idea to implement.

Testing with a ToneMatrix like application on my own Android based phone showed that the size of the Tone Matrix can be detrimental to the user experience, as depending on finger size it can be very hard to click on a specific cell in the matrix. This showed that control of matrix size could be a good feature for optimized user experience.

After the initial research and idea generation, a list of  vaguely defined features that was wanted in the final application was made.

  1. Tone Matrix
  2. Control of matrix size
  3. Stop and Start Button
  4. Save and Load Feature
  5. Addition of other Instruments(ex. drums)
  6. Control of Instrument Tempo (Quarter time, half time etc.)
  7. Control of Matrix Tempo

1.3 Final Draft

Adding these features within the given timeframe seemed like a reasonable task and from here the project went on to the drafting phase. Going through multiple avenues of design a final draft was chosen for implementation, which can be seen below(original was a paper version, this one was created later on a pc for upload). The draft was heavily inspired by some of AudioKits own examples.draft with comments

2. Introduction – Portfolio 2

A video of the “final” product and use of all the features that was fully implemented

2.1 Motivation

In this project i wanted to create a Tone Matrix as explained in the design phase of  this blog. My interest in this area comes from playing with Audiotools original Tone Matrix, which i found to be a very relaxing an fun product that had a great fundemental design. However the original products biggest issue was the very simplicity that was so great about. It lacked features that could improve the experience. You had the matrix, and only the matrix, in the original product.
As i was being introduced to the AudioKit framework, and all it’s different tools i felt that this had a great potential to improve this allready great product in a new and different way.

2.2 Aim & User experience

With my application i didn’t want to lose the simplicity of the original tonematrix, but only add optional features that worked on top of the existing product and didnt interfere with it. The user experience should stay playful and relaxing, but with options for further avenues of exploration if the user so desired. I also wanted the user to be able to save their matrix, so once they reentered the application they didn’t have to start all a new.

3. Implementation

Development of the application was done in Xcode 7, using Apples new programmering language Swift 2.0.

The AudioKit 3.0 Framework was used to implement the audio based aspects of the application.

3.1 Generating the scene

While most of the UI in the application is created by .nib files through the Xcode editor, the Tone Matrix itself is created programatically, because we want to be able to change the size of the matrix dynamically in run time.

    override func viewDidLoad() {
        super.viewDidLoad()
   
        for(var i =0;i < 16;i++)
		{
			let pong = AKSampler();
			pong.loadWav("Sounds/wavtones/tone"+String(i));
			pongArray.append(pong);
			mixer.connect(pongArray[i]);
		}
		
        AudioKit.output = mixer;
        AudioKit.start();

               // Do any additional setup after loading the view, typically from a nib.
        createMatrix();
        matrixTimer = NSTimer.scheduledTimerWithTimeInterval(matrixDelay, target: self, selector:"matrixPlayFunc", userInfo: nil, repeats: true);
    }

In the viewDidLoad we start by initializing our AKSamplers, connect them all to a single AKMixer object and add them to an array for easy access. These samplers will be used to play the tones in our Tone Matrix. After that it sets AudioKit’s output to mixer, to which we just connected our samplers and starts AudioKit. Then we run our createMatrix() function which creates the Tone Matrix. As seen in the code, the createMatrix uses a parameter called matrixSize to determine how many buttons it should create. This is the key variable that determines the size of the Tone Matrix, and how we loop through our arrays. The code itself is a for loop which creates a UIButton, tags it, puts it in an array, and creates an array to keep track of active buttons. Finally we create an instance of the NSTimer class.

  func createMatrix() {
        
        let screenSize: CGRect = UIScreen.mainScreen().bounds
        let screenWidth = screenSize.width
        let screenHeight = screenSize.height
        //	 let midder = (screenHeight-screenWidth)/2
        
        let squareSize = (screenWidth < screenHeight) ? screenWidth/(12.5*CGFloat(matrixSize)/8) : screenHeight/(12.5*CGFloat(matrixSize)/8)
        //let squareSize = screenWidth/12.5
        var columnNumber = 0;
        for(var i = 0;i < (matrixSize*matrixSize);i++){
            
            let rowNumber = i%matrixSize
            if(i % matrixSize == 0){
                columnNumber += 1
            }
            let button = UIButton(type: UIButtonType.System) as UIButton
            button.frame = CGRectMake(-squareSize+(squareSize/2*CGFloat(columnNumber)+(squareSize*CGFloat(columnNumber))), 70+(squareSize/2*CGFloat(rowNumber)+(squareSize*CGFloat(rowNumber))), squareSize, squareSize)
            
            button.backgroundColor = UIColor.init(colorLiteralRed: 0.32, green: 0.32, blue: 0.32, alpha: 1)
            button.setTitle("",forState: UIControlState.Normal)
            button.addTarget(self, action: "buttonAction:", forControlEvents: UIControlEvents.TouchUpInside)
            button.tag = i;
            buttonArray.append(button);
            activeArray.append(false);
            self.view.addSubview(button)   
        }
    }

By using a bit of math we make sure that regardless of the screenSize or the matrixSize, it will always fit within our iPhones screen. Notice (line: 22) that when we create a button we add a reference to what function execute should when this button is pressed.

    func buttonAction(sender:UIButton!){
        let btn = sender  
        if(activeArray[btn.tag] == false)
        {
            activeArray[btn.tag] = true;
            btn.backgroundColor = UIColor.init(colorLiteralRed: 0.75, green: 0.75, blue: 0.75, alpha: 1)          
        }
        else{
            activeArray[btn.tag] = false;
            btn.backgroundColor = UIColor.init(colorLiteralRed: 0.32, green: 0.32, blue: 0.32, alpha: 1)
        }
    }

This is the function that activates and deactivates the cells in our matrix through button clicks.
Here we see that the tag attached to every button corresponds with a value stored in our activeArray. This way when a button is pressed we set the corresponding value in the activeArray to true, and colours the cell to match its current state.

3.2 Executing the Tone Matrix

Since the Tone Matrix works by sequencing through our matrix, we need a periodically timed event to know when we want to play our tones. In Swift this is easily done by using the NSTimer Class. Using the NSTimer object we instantiate we can use its scheduledTimerWithTimeInterval function to create a periodically timed event that runs a function and then repeats itself after a given time interval. We use this to run the function matrixPlayFunc which is shown below.

    func matrixPlayFunc(){
        activeColumn = activeColumn%matrixSize;
        reDrawWhites(activeColumn-1);
      
        for(var i = 0;i < matrixSize;i++)
        {
            if(activeArray[i+activeColumn*matrixSize] == true)
            {
                buttonArray[i+activeColumn*matrixSize].backgroundColor = UIColor.init(colorLiteralRed: 1, green: 1, blue: 1, alpha: 1)
                pongArray[i].playNote();
            }
        }
        activeColumn++;
    }

This function loops through a column of buttons/cells in our matrix, checks if they are activated, and if this is the case it plays the corresponding tone for that matrix cell. We also colour our cell white to indicate that this is where the tone is being played, which results in a nice animation when it loops through.
The first line makes sure that if we have reached the end of our matrix we jump back to the start. The second line runs a function that colours all white cells in the prior column(cause we know only the prior column has the possbility of being white) back to their “activated” colour. If it stayed white it wouldn’t give the animation of movement in our Tone Matrix. Lastly we increment our activeColumn so that next time the function runs it will play the cells in the next column. Notice (line: 7) that we use the “activeArray” to see if a button is pressed or not.

3.3 Menu Items

Now we have the fundemental functionality of a Tone Matrix in place. The next step is to add a start and stop button, a save function and a load function.

Lets start with the play button.

    @IBAction func playButtonAction (sender:UIBarButtonItem! ) {
        if(playing == true)
        {
            matrixTimer!.invalidate();
            matrixTimer = nil;
            playing = false;            
        }else
        {
            if(matrixTimer == nil)
            {
            matrixTimer = NSTimer.scheduledTimerWithTimeInterval(matrixDelay, target: self, selector:"matrixPlayFunc", userInfo: nil, repeats: true);
            playing = true;
            }
        }     
    }

By using an @IBAction we can tie an action with one of the buttons created in the Xcode editor. We do this through the mainboard in Xcode by dragging the action onto the button we wish to correlate it with. This functions runs when the leftmost item on the bottom toolbar is pressed down.

123123

By using a boolean we can apply both the play and pause functionality to the same button. The Tone Matrix stops playing because we invalidate the timer which runs the function that plays the tones. Invalidating the timer completely removes it from the application, therefore when we want to play we need to instantiate a new instance of our NSTimer class.

The save and load feature uses similar logic when pairing up our button with an action.

   @IBAction func saveArray(sender:UIBarButtonItem!){
        userDefaults.setObject(activeArray, forKey: "derp");
    }
   
    @IBAction func loadArray(sender:UIBarButtonItem!){
        let tmp: [Bool] = userDefaults.objectForKey("derp") as! [Bool]
        activeArray = tmp;
        reDraw();
    }

To save our data persistently we save our activeArray, which is a reference to which buttons are pressed, in the userDefaults. userDefaults is a NSUserDefaults object, which saves the data in a file somewhere in the application.

When we load our data we ask it to give it to us as a boolean array, as we know that this is how we saved it. We assign the value to our activeArray and then we use the reDraw function to redraw our new setup of the Tone Matrix.

3.4 Additional Matrix functionality

In the design phase it was decided that we wanted to add some extra functionality to our Tone Matrix. Two of these was implemented in the final product, which was control of the matrix tempo, and resizing of the matrix.

    @IBAction func tempoChange(sender:UISlider!) {
        
        matrixDelay = Double(sender.value)/100;
        sliderLabel.text = String(sender.value);
    
        if(playing == true)
        {
            matrixTimer.invalidate();
            matrixTimer = nil;
            matrixTimer = NSTimer.scheduledTimerWithTimeInterval(matrixDelay, target: self, selector:"matrixPlayFunc", userInfo: nil, repeats: true);
        }    
    }

This function controls the tempo of the Tone Matrix by using a slider that adjust the time interval for our NSTimer thus speeding or slowing down the time between tones being played. If the Tone Matrix is currently playing we reinstantiate our timer with the new values once the user releases the slider. If it’s not playing it will use the new value once the user presses play.

    @IBAction func resizeMatrix(sender:UIButton!){
        
        removeMatrix();   
        matrixSize = Int(sender.currentTitle!)!
        activeColumn = 0;
        activeArray = [];
        buttonArray = [];
        createMatrix();
    }

This function simply runs a function that deletes all buttons in our buttonArray, it empties all arrays with critical data for when we need to create our matrix again and resets the activeColumn so when we start playing again it will do so from the first line in the Tone Matrix.

Here is the code snipper for the reDrawWhites and reDraw functions that has been used in other functions.

    func reDraw()
    {
        
        for(var i = 0;i < activeArray.count;i++)
        {
            for view in self.view.subviews as [UIView]
            {
                if let btn = view as? UIButton {
                    if activeArray[btn.tag] == true{
                    btn.backgroundColor = UIColor.init(colorLiteralRed: 0.75, green: 0.75, blue: 0.75, alpha: 1)
                    }else{
                        btn.backgroundColor = UIColor.init(colorLiteralRed: 0.32, green: 0.32, blue: 0.32, alpha: 1)                      
                    }
                }
            }
        }
        
    }
    
    func reDrawWhites(var previousColumn:Int){
        if(previousColumn == -1)
        {
            previousColumn = matrixSize-1;
        }
        for(var i = 0;i < matrixSize;i++)
        {  
            if(buttonArray[i+previousColumn*matrixSize].backgroundColor == UIColor.init(colorLiteralRed: 1, green: 1, blue: 1, alpha: 1)){
                buttonArray[i+previousColumn*matrixSize].backgroundColor = UIColor.init(colorLiteralRed: 0.75, green: 0.75, blue: 0.75, alpha: 1);      
            }
        }
    }

These functions simply loop through button objects, where the reDrawWhites is limited to a column of specifically coloured cells, and it colours them based on their activity state. Notice (line: 21) the first if sentence in reDrawWhites. Due to the fact we select the column behind our activeColumn (activeColumn-1), we get a -1 value when we are on the first column of the matrix. What we want to select however is the very last column which can be represented by matrixSize-1. This makes sure that the last column is recoloured as well. the reDrawWhites function can be substituted by the reDraw function but my simulator did not like all this recolouring occuring so frequently. Therefore this less intensive function was made to redraw when only specific cells needed it.

4. Conclusion

Looking at my final draft for a design its obvious that i have not met the requirements i had foreseen when starting this project. Generating acceptable sounding instruments that were running in line with the Tone Matrix proved more difficult than anticipated, and after much time used on it, this feature was abandoned.

However i do think that the core functionality of the Tone Matrix runs very well, and while i only implemented a very basic save/load function it can easily be expanded upon by adding an option to name the save file and using this name as a key. The “save file” could then be loaded out of another view controller which holds them all in a browsable table view.

Unfortunately i did not get to test my product on any users but myself once i had the functionality at a level where this was possible. A usability test of the application could of course show unforeseen problems as a developer don’t know how the user will interact with the product.

Overall i feel some aspects of the project could be improved greatly by simply adding more feedback to the user. For example it is not very obvious that your file has been saved. Using Icons instead of text would also be a good improvement to the design.

The project has been a learning experience for me, and my understanding of working with the Xcode editor, programming on a mac computer, as well as Swift 2.0 have increased dramastically during the Tone Matrix project.
Overall i am happy with the results, as it is my first Swift project, and i made an actual optimized version of the original Tone Matrix i remember from before i started the project. Compared to the Tone Matrix like products on the market for iOS mine is a bit below in terms of functionality and optimization however.

 

The Source code for the Tone Matrix Project is available here: google drive

Leave a Reply