Tutorial05 Plus2 - Atom methods and Instantiation arguments

Plus1 has the basic behaviour of Max's built-in plus (+) object, but there are several differences. To truly duplicate the functionality of the built-in plus object our class must have the ability to accept an instantiation argument that sets the addend. Also, our class has to operate both in integer and floating-point modes: if there is an instantiation argument and it is a float, the object should operate in floating-point mode. Otherwise it should operate in integer mode and truncate any incoming floats. If this functionality doesn't sound familiar, open up the help file for the + object and play with it for a minute before you read the code below.

import com.cycling74.max.*;

public class Plus2 extends MaxObject {

	private Atom addend = Atom.newAtom(0);
	
	public Plus2(Atom[] args) {
		declareIO(2,1);
		if (args.length > 0) {
			if (args[0].isInt() || args[0].isFloat()) {
				addend = args[0];
			}
		} 
	}
	
	public void inlet(int i) {
		if (getInlet() == 0) {
			if (addend.isInt()) {
				outlet(0, i + addend.getInt());
			} else {
				outlet(0, (float)i + addend.getFloat());
			}
		} else {
			if (addend.isInt()) {
				addend = Atom.newAtom(i);
			} else {
				addend = Atom.newAtom((float)i);
			}
		}
	}
	
	public void inlet(float f) {
		if (getInlet() == 0) {
			if (addend.isInt()) {
				outlet(0, (int)f + addend.getInt());
			} else {
				outlet(0, f + addend.getFloat());
			}
		} else {
			if (addend.isInt()) {
				addend = Atom.newAtom((int)f);
			} else {
				addend = Atom.newAtom(f);
			}
		}
	}
}

The first change to note is that the addend variable used to be an int and is now an Atom. We use an Atom because it can represent either an integer or a floating-point number, which will be convenient for the dual-mode operation of this object. Unlike classes which allow you to create instances of the class by calling a constructor method directly, the Atom class requires that you call one of the Atom.newAtom factory methods provided by the API. The Atom.newAtom(int) method is used to initialize addend to represent the integer 0 by default.

The next change to note is that constructor method takes an array of Atoms as an argument. Any instantiation arguments appended after the classname in the Max object box will be passed to this constructor as an array of Atoms. So if a user creates a new object box in Max and types "mxj Plus2 74", the args array would contain a single integer Atom with the value 74.

Note that the method used to declare the inlets and outlets is different: declareIO(int, int) takes two integers and creates that many inlets and outlets, all of type DataTypes.ALL. After declaring these inlets and outlets the Plus2 class's constructor method tests to see if the length of the args array is bigger than 0. In Java the length of an array can be determined by treating the array like an object and accessing its length field as we do above. If no instantiation arguments have been supplied the length of the args array will be zero and the program flow will exit the constructor method with the default addend of the integer 0 intact.

However, if there is at least one Atom in the array a further test takes place. The isInt() and isFloat() methods are used to find out if the first element of args represents either an integer or floating-point number, and if so this first element is assigned to addend.

Two inlet methods are necessary because our class is now able to accept both types of numeric input. The if-else logic splits the flow of execution according to the inlet that received input. If a number comes in the right inlet, a new Atom is created and assigned to addend. If a number comes in the left inlet, the outlet method sends the addition of addend and the incoming value out the left outlet. Both inlet methods use isInt to determine whether addend represents an integer or a floating-point number, and if neceessary the input is cast to the proper type before addition or storage in addend.

C programmers might find it unusual that a new Atom must be created every time we want to change the value represented by addend. The Atom class is immutable, which means that once an instance has been created the value that it represents cannot change. To learn about the good reasons to design a class to be immutable, we refer you to Joshua Bloch's excellent book Effective Java.

There is still some minor functionality in the built-in plus (+) object that is not in our Plus2 class. If an instance of Plus2 receives a bang in the left inlet it should output the last result again, and if it receives a list the object responds as if the second element had been sent in the right inlet and the first element in the left inlet. To test your understanding you may want to edit the Plus2 class to support this missing functionality.