AMMS applications - AMMSMIDLET

This section provides the source code for the AMMSMIDlet example. For a complete Eclipse project ZIP file, see the S60 MIDP SDK, from Third Edition Feature Pack 1 onwards.

The examples include the following classes:

AMMSMIDLET


package com.nokia.mid.mansion;

import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;

/**
 * AMMSMIDlet class.
 *
 */
public class AMMSMIDlet extends MIDlet implements CommandListener {

    Display display;
    MansionCanvas canvas;		// The main screen
    private Command exitCommand = new Command("Exit", Command.EXIT, 1);
    private Command toggleCommand = new Command("Start", Command.SCREEN, 1);
    private Command helpCommand = new Command("Help", Command.HELP, 1);
    private Command aboutCommand = new Command("About", Command.HELP, 2);
    private Form helpScreen, aboutScreen;

    /*
     * Create the canvas
     */
    public AMMSMIDlet() {
	display = Display.getDisplay(this);
	canvas = new MansionCanvas(display);
	canvas.addCommand(exitCommand);
	canvas.addCommand(toggleCommand);
	canvas.addCommand(helpCommand);
	canvas.addCommand(aboutCommand);
	canvas.setCommandListener(this);
    }

    public void startApp() throws MIDletStateChangeException {
    	canvas.start();
    }

    public void pauseApp() {
    	canvas.pause();
    }

    public void destroyApp(boolean unconditional) throws MIDletStateChangeException {
    	canvas.destroy();
    }

    /*
     * Respond to a command issued on the Canvas.
     */
    public void commandAction(Command c, Displayable s) {
		if (c == toggleCommand) {
		    if (canvas.isPaused()) canvas.start();
		    else canvas.pause();
		} 
		else if (c == helpCommand) {
		    canvas.pause();
		    showHelp();
		} 
		else if (c == exitCommand) {
		    try {
				destroyApp(false);
				notifyDestroyed();
			} catch (MIDletStateChangeException ex) {
		    }
		} 
		else if (c == aboutCommand) {
		    canvas.pause();
		    showAbout();
		}
    }

    /*
     * Put up the help screen. Create it if necessary.
     * Add only the Resume command.
     */
    void showHelp() {
		if (helpScreen == null) {
		    helpScreen = new Form("Walking Help");
		    helpScreen.append("^ = walk forward\n");
		    helpScreen.append("v = walk backwards\n");
		    helpScreen.append("< = turn left\n");
		    helpScreen.append("> = turn right\n");
		    helpScreen.append("fire = change reverb manually\n");
		}
		helpScreen.addCommand(toggleCommand);
		helpScreen.setCommandListener(this);
		display.setCurrent(helpScreen);
    }

    /*
     * Put up the about screen. Create it if necessary.
     * Add only the Resume command.
     */
    void showAbout() {
		if (aboutScreen == null) {
		    aboutScreen = new Form("About AMMS Mansion demo");
		    aboutScreen.append("This MIDlet demonstrates the 3D audio capabilities of AMMS API.\n");
		    aboutScreen.append("\n");
		    aboutScreen.append("Copyright (c) 2006 Nokia. All rights reserved.\n");
		    aboutScreen.append("\n");
		    aboutScreen.append("\n");
		}
		aboutScreen.addCommand(toggleCommand);
		aboutScreen.setCommandListener(this);
		display.setCurrent(aboutScreen);
    }
}

House

package com.nokia.mid.mansion;

import javax.microedition.lcdui.Graphics;

/**
 * Contains the walls and the reverb settings.
 *
 */
public class House {
    public static final int NUM_REV_PRESETS = 10;
    private static final int ALLEY=0,ARENA=1, AUDITORIUM=2,BATHROOM=3,CAVE=4,
	HALLWAY=5,HANGAR=6,LIVINGROOM=7,MOUNTAINS=8,ROOM=9;
    static final String REV_PRESETS[] = {
		"alley", "arena", "auditorium", "bathroom", "cave",
		"hallway", "hangar", "livingroom", "mountains", "room"
    };

    // coordinates and reverb settigns of the rooms:
    private static final int[][] ROOMS =
    {//{Y_MIN,Y_MAX,X_MAX,REVERB_PRESET} 
	{6000,6000,0,MOUNTAINS}, // dummy (sentinel)
	{3000,10000,4000,MOUNTAINS}, //yard
	{6000,7000,8000,HALLWAY},
	{2000,10000,19000,AUDITORIUM},
	{3500,3500,19000,ALLEY} // dummy (sentinel)
    };

    public House (){
	
    }

    /**
     * @return true, if given coordinates are inside of the house.
     */
    public boolean isInside(int x, int y) {
		boolean inside = false;
		final int room = inWhichRoom(x);
		if (y+100 < ROOMS[room][1] && y-100 > ROOMS[room][0])
		    inside = true;
		return inside;
    }
    
    /**
     * Tells in which room is the given x-coordinate in.
     *
     * @return room index
     */
    public int inWhichRoom(int x) {
		int room = ROOMS.length-1;
		for (int i = 0; i<ROOMS.length-1; i++) {
		    if (x > ROOMS[i][2]) {
		    	room = i+1;
		    } else {
		    	break;
		    }
		}
		if (room >= ROOMS.length)
		    System.out.println("bug in House.inWhichRoom()! room=" + room);
		return room;
    }

    /**
     * Gets the reverb preset name for the given room
     */
    public static String presetName(int room) {
    	if(room < ROOMS.length && room >= 0) {
    		return REV_PRESETS[ROOMS[room][3]];
    	} else {
    		return "";
    	}
    }

    /**
     * Gets reverb preset name by index
     */
    public static String presetNameByIndex(int index) {
    	if(index < NUM_REV_PRESETS && index >= 0) {
    		return REV_PRESETS[index];
    	} else {
    		return "";
    	}
    }
     
   /**
     * Draws the house to given Graphics.
     * @param dx x translation in millipixels
     * @param dy y translation in millipixels
     * @param rot rotation in deci-angles
     */
    public void draw(Graphics g, int dx, int dy, int rot) {
    	for (int i=1; i
    	for (int i=1; i

MansionCanvas


package com.nokia.mid.mansion;

import javax.microedition.lcdui.*;

/**
 * Handles canvas and also Spectator.
 */
public class MansionCanvas extends Canvas {
    final Display display;
    final int width, height;
    boolean paused;
    final static int NUM_SOURCES = 4;
    private Source[] sources = new Source[NUM_SOURCES];
    private House house;
    private Walker walker;
    int forcedReverb = -1;
    String msg = "";
    private int msgColor = 0xcc11cc;
 
    public MansionCanvas(Display d) {
		display = d;
		paused = true;
		width = getWidth();
		height = getHeight();
		house = new House();
		
		// init sources:
		sources[0] = new Source(6000, 6500, "/largedog16k.wav", house, this);
		sources[1] = new Source(10000, 3500, "/budgie_16k.wav", house, this);
		sources[2] = new Source(1100, 6000, "/owl_16k.wav", house, this);
		sources[3] = new Source(1150, 6000, "/cock_16k.wav", house, this);
	
		for (int i = 0; i<NUM_SOURCES; i++){
		    System.out.println("starting source "+ i);
		    Thread t = new Thread(sources[i]);
		    t.start();
		}
		walker = new Walker();
		msg = "Welcome to Mansion!";
    }

    /**
     * Draws everything.
     */
    protected void paint(Graphics g) {
        int x_min = g.getClipX();
        int y_min = g.getClipY();
        int w = g.getClipWidth();
        int h = g.getClipHeight();

		// Empty the frame 
		g.setColor(0xffffff);
		g.fillRect(x_min, y_min, w, h);
		g.translate(width/2, height/2); // move origin to the center
	
		// Draw sources
		for (int i = 0; i<NUM_SOURCES; i++) {
		    sources[i].draw(g, walker.getX(), walker.getY(), walker.getRot());
		}
	
		// Draw house (walls and doors)
		house.draw(g, walker.getX(), walker.getY(), walker.getRot());
	
		// Draw player (walker) at origin:
		g.setColor(0xdd2222);
		g.fillRect(-4, -1, 9, 2); // shoulders
		g.setColor(0x66660a);
		g.drawLine(0, -3, 0, -3); // nose
		g.setColor(0);
		g.fillArc(-2, -2, 5, 5, 0, 360); // head
		g.translate(-width/2, -height/2); // reset translation
	
		// Draw the frame
		g.setColor(0);
		g.drawRect(0, 0, width-1, height-1);
	
		// Draw message:
        if (msg != null) {
            g.setColor(0xffffff);
            g.setClip(0, height-14, width, height);
            g.fillRect(0, height-20, width-2, 18);
            g.setColor(msgColor);
            g.drawString(msg, 5, height-14, 0);
		    g.setColor(0);
            g.drawRect(0, 0, width-1, height-1);
        }
		occludeSources();
    }//paint()

    /**
     * Starts sources that are in the same room (or space) and stops the rest
     */
    private void occludeSources() {
		int listeningRoom = house.inWhichRoom(walker.getX());
		for (int i = 0; i<NUM_SOURCES; i++) {
		    if(listeningRoom == house.inWhichRoom(sources[i].getX())) {
		    	sources[i].start();
		    } else {
		    	sources[i].stop();
		    }
		}
    }

    public void keyPressed(int keyCode) {
    	keyAction(keyCode);
    }

    public void keyRepeated(int keyCode) {
    	keyAction(keyCode);
    }
    
    private void keyAction(int keyCode) {
		int action = getGameAction(keyCode);	
		switch (action) {
		case LEFT:
		    walker.rotateLeft();
		    break;
		case RIGHT:
		    walker.rotateRight();
		    break;
		case UP:
		    walker.moveForward(house);
		    break;
		case DOWN:
		    walker.moveBackward(house);
		    break;
		case FIRE:
		    forcedReverb += 1;
		    forcedReverb = forcedReverb % House.NUM_REV_PRESETS;
		    walker.forceReverb(forcedReverb);
		    break;
		}
		msg = "" + walker.getRevName();
		/*msg = "X:" + walker.getX() + "Y:" + walker.getY()
		    + "r:" + walker.getRot()*10 + " " + walker.getRevName();*/
		repaint();
    }//keyAction()

    /**
     * Closes the sound sources.
     */
    void destroy() {
		for(int i=0; i< NUM_SOURCES; i++) {
	 	    sources[i].destroy();
	 	}
    }

    boolean isPaused() {
    	return paused;
    }

    void pause() {
		if (!paused) {
		    paused = true;	
		}
		repaint();
    }

    void start() {
		if (paused) {
		    paused = false;
		    display.setCurrent(this);
		}
		repaint();
    }
}

Sources

package com.nokia.mid.mansion;

import javax.microedition.lcdui.Graphics;
import javax.microedition.media.*;
import javax.microedition.media.control.*;
import java.io.InputStream;
import java.io.IOException;
import javax.microedition.amms.control.audio3d.LocationControl;
import javax.microedition.amms.*;

import java.util.Random;
import javax.microedition.lcdui.Canvas;

/**
 * Sound source with graphics, too
 */
public class Source implements Runnable {
    final static int BALL_RADIUS = 5;
	final static int BALL_DIAM = BALL_RADIUS*2;
    private int x = 0, y = 0; //sound source's location
    private Player p;
    private VolumeControl volC;
    private LocationControl locC;
    private SoundSource3D ss3D;

    /** keeps the mover thread active until true*/
    protected boolean closed = false;

    // needed for collision detection in random movements:
    House house = null; 
    Canvas canvas = null; // for repaint after movement

    static Random random = new Random();
    private static int yellow = 0xcccc00;

    public Source(int x, int y, String fileName, House house, Canvas canvas) {
		this.x=x;
		this.y=y;
		this.house = house;
		this.canvas = canvas;
	
		InputStream is = getClass().getResourceAsStream(fileName);
		if (is == null)
			System.out.println("MIDlet: error creating InputStream of" + fileName);
		try {
			System.out.println("MIDlet: Creating Player " + fileName);
			p = Manager.createPlayer(is, "audio/X-wav");	  
			if (p == null)
				System.out.println("error creating Player of" + fileName);
			p.realize();
			p.setLoopCount(-1); //indefinetely
			System.out.println("MIDlet: realized: " + fileName);
		} catch (IOException e) {
			System.out.println(e);
		} catch (MediaException e) {
			System.out.println(e);
		}
		volC=(VolumeControl)p.getControl("VolumeControl");
		if (volC!=null) {
			System.out.println("MIDlet: got VolumeControl");
			int v = volC.setLevel(100);
			System.out.println("MIDlet: set Volume to "+v);
		}
	       
		try {
			ss3D = GlobalManager.createSoundSource3D();        
			if (ss3D != null) {
		        System.out.println("MIDlet: got a new SS3D");
		        ss3D.addPlayer(p);
		        System.out.println("MIDlet: Player added to a SS3D");
		        locC=(LocationControl)ss3D.getControl("javax.microedition.amms.control.audio3d.LocationControl");
		        if (locC != null) {
		        	System.out.println("MIDlet: got LocationControl from a SS3D");
		        	locC.setCartesian(x, 0, y);
		        } else {
		        	System.out.println("LocationControl not available for " + fileName);
		        	ss3D.removePlayer(p);
		        }
		        p.prefetch();
			}
	    } catch (MediaException e) {
	    	System.out.println(e);
	    }	
    }

    /**
     * Deinits players
     */
    protected void destroy() {
		closed = true;
		if(p != null) { 
		    p.close();
		}
    }

    public final void start() {
    	try {
    		p.start();
    	} catch (MediaException e) {
		    System.out.println(e);
		} catch (IllegalStateException ise) {
			    //synchronization problems with close.
		}
   }

    public final void stop() {
    	if(p.getState() == Player.STARTED) {
    		Thread t = new Thread () {
    			public void run() {
    				try {
    					p.stop();
    				} catch (MediaException e) {
    					System.out.println(e);
    				} catch (IllegalStateException ise) {
    					//synchronization problems with close.
    				}
    			}
    		};
	    t.start();
    	}
    }

    public final void setLocation(int x, int y) {
		this.x=x;
		this.y=y;
		if (locC != null) { 
			locC.setCartesian(x, 0, y);
		} 
    }

    public final int getX() {
    	return x;
    }
    
    final int getY() { //package visibility for FastSource
    	return y;
    }

    public void run() {
		int tempX = 0;
		int tempY = 0;
		while(!closed) {
		    tempX = x + (random.nextInt() & 256) -128;
		    tempY = y + (random.nextInt() & 256) -128;
		    if (house.isInside(tempX, tempY)) {
				setLocation(tempX, tempY);
				canvas.repaint();
		    }
		    try {
		    	Thread.sleep(250);
		    } catch (InterruptedException e) {}
		}
    }

    /**
     * Draws the sound source to given Graphics.
     * @param dx x translation in millipixels
     * @param dy y translation in millipixels
     * @param rot rotation in deci-angles
     */
    public void draw(Graphics g, int dx, int dy, int rot) {
		int newX = Trig.transXS(x-dx, y-dy, rot) ;//+g.getClipWidth()/2;
		int newY = Trig.transYS(x-dx, y-dy, rot) ;//+g.getClipHeight()/2;
		drawFace(g, newX, newY);   
    }

    /**
     * Draws a face to given graphics
     */
    void drawFace(Graphics g, int x, int y) { 
		// Draw ball:
		g.setColor(yellow);
		g.fillArc(x-BALL_RADIUS, y-BALL_RADIUS, BALL_DIAM, BALL_DIAM, 0, 360);
		// Draw eyes:
		g.setColor(0);
		g.fillArc(x-2, y-2, 2, 2, 0, 360);
		g.fillArc(x+2, y-2, 2, 2, 0, 360);
		// mouth:
		g.drawArc(x-BALL_RADIUS+1, y-BALL_RADIUS+1, BALL_DIAM-3, BALL_DIAM-3, 200, 140);
    }
}

Trig

package com.nokia.mid.mansion;

/**
 * Trigonometrical function estimates.
 *
 */
public class Trig{
    private final static int SIN[]=
    {   
		0, 174, 342, 500, 643, 766, 866, 940, 985, 1000, 985, 940, 866, 766,
		643, 500, 342, 174, 0, -174, -342, -500, -643, -766, -866, -940, -985,
		-1000, -985, -940, -866, -766, -643, -500, -342, -174 
	};

    private final static int COS[]= 
    {
		1000, 985, 940, 866, 766, 643, 500, 342, 174, 0, -174, -342, -500, -643,
		-766, -866, -940, -985, -1000, -985, -940, -866, -766, -643, -500, -342,
		-174, 0, 174, 342, 500, 643, 766, 866, 940, 985 
	};

    /**
     * Sine estimator
     *
     * @param alpha in deci-degrees, between 0 and 35
     * @return in thousandths
     */
    private static int sin(int alpha) {
    	return SIN[alpha];
    }
    
    /**
     * Cosine estimator
     *
     * @param alpha in deci-degrees, between 0 and 35
     * @return in thousandths
     */
    private static int cos(int alpha) {
    	return COS[alpha];
    }  

    /**
     * Calculates the rotation
     *
     * @param theta in deci-degrees, between 0 and 35
     * @return new x 
     */
    public static int transX(int x, int y, int theta){
    	return (x * cos(theta) - y * sin(theta))/1000;
    }

    /**
     * Calculates the rotation
     *
     * @param theta in deci-degrees, between 0 and 35
     * @return new y
     */
    public static int transY(int x, int y, int theta){
		// minus because of y-coordinate of the screen upside down(?)
		return -(x * sin(theta) + y * cos(theta))/1000;
    }

   /**
     * Calculates the rotation for the screen coordinates directly
     *
     * @param theta in deci-degrees, between 0 and 35
     * @return new x 
     */
    public static int transXS(int x, int y, int theta){
    	return -(x * cos(theta) - y * sin(theta))/50000;
    }

    /**
     * Calculates the rotation for the screen coordinates directly
     *
     * @param theta in deci-degrees, between 0 and 35
     * @return new y
     */
    public static int transYS(int x, int y, int theta){
		// minus because of y-coordinate of the screen upside down(?)
		return (x * sin(theta) + y * cos(theta))/50000;
    }
}

Walker

package com.nokia.mid.mansion;

import javax.microedition.media.*;
import javax.microedition.amms.Spectator;
import javax.microedition.amms.GlobalManager;
import javax.microedition.amms.control.audio3d.OrientationControl;
import javax.microedition.amms.control.audio3d.LocationControl;
import javax.microedition.amms.control.audioeffect.ReverbControl;

/**
 * Spectator functionality.
 *
 */
public class Walker {
    private static final int STEP = 200; //step size in mm
    private Spectator spectator;
    private LocationControl specLocC; // locC of spectator
    private OrientationControl specOriC; // OriC of spectator
    private ReverbControl reverb;
    private String revName = "none";
    private int specX = 1000, specY = 6000; // spectator location
    private int specRot = 0; // spectator rotation
    // for checking the needs for a reverb settings updates:
    private int prevRoom = -999; 

    public Walker (){
    	initSpectator();
    }

    /**
     * 	Init spectator and get controls for it.
     */
    private void initSpectator() {   
	    try {
	    	spectator = GlobalManager.getSpectator();
	    } catch (MediaException me) {
	    	System.out.println(me);
	    }
    
	    if(spectator != null) {
	    	if ((specLocC =
	    		(LocationControl)spectator.getControl("javax.microedition.amms.control.audio3d.LocationControl"))
	    		!= null) {
	    			specLocC.setCartesian(specX, 0, specY); 
	    	} else {
	    		System.out.println("Spectator couldn't get LocationControl.");
	    }
      
	    if ((specOriC =
	    	(OrientationControl)
	    	spectator.getControl("javax.microedition.amms.control.audio3d.OrientationControl"))
	    	!= null) {
	    		specOriC.setOrientation(specRot*10, 0, 0); 
	    } else {
	    	System.out.println("Spectator couldn't get OrientationControl.");
	    }
      
	    try {
	    	if ((reverb =
	    		(ReverbControl)GlobalManager.getControl("javax.microedition.amms.control.audioeffect.ReverbControl"))
	    		!= null) {
	    			try {
	    				reverb.setPreset("mountains");
	    			}
	    			catch (IllegalArgumentException iae) {
	    				System.out.println(iae);
	    			}
	    			reverb.setEnabled(true);
	    			if(!(reverb.isEnabled()))
	    				System.out.println("MIDlet: enabling reverb failed.");
	    	} else {
	    		System.out.println("Spectator couldn't get Reverb.");
	    	}
      }
      catch (IllegalArgumentException iae) {
        System.out.println(iae);
      }
    }
  }//initSpectator()

    public void rotateLeft() {
		// rotate spectator left 
		specRot = specRot - 1;
		if (specRot == -1)
		    specRot = 35;
		updateSpecRot(specRot);
    }

    public void rotateRight() {
		// rotate spectator right
		specRot = (specRot + 1)%36;
		updateSpecRot(specRot);
    }
    
    public void moveForward(House house) {
		int newSpecX = specX + Trig.transX(0, STEP, specRot);
		int newSpecY = specY + Trig.transY(0, STEP, specRot);
		updateSpecLoc(newSpecX, newSpecY, house);
    }

    public void moveBackward(House house) {
		int newSpecX = specX + Trig.transX(0, -STEP, specRot);
		int newSpecY = specY + Trig.transY(0, -STEP, specRot);	
		updateSpecLoc(newSpecX, newSpecY, house);
    }

    public int getX() {
    	return specX;
    }

    public int getY() {
    	return specY;
    }

    public int getRot() {
    	return specRot;
    }

    private void updateSpecRot(int newRot) {
		newRot += 18;
		if(specOriC != null) specOriC.setOrientation(newRot*10, 0, 0);
    }

    /**
     * Updates location if collision to the wall doesn't prevent.
     * Updates possible new reverb settings as well, if the spy moves to a new room.
     */
    private void updateSpecLoc(int newX, int newY, House house) {
    	if (house.isInside(newX, newY)) {
		    specX = newX;
		    specY = newY;
		    if(specLocC != null)
			specLocC.setCartesian(newX, 0, newY);
		    
		    // update reverb settings if necessary
		    int currentRoom = house.inWhichRoom(specX);
		    if (currentRoom != prevRoom) {
				prevRoom = currentRoom;
			    if(reverb != null) {
					reverb.setPreset(House.presetName(currentRoom));
					revName = House.presetName(currentRoom);
					System.out.println("New reverb: " + House.presetName(currentRoom));
			    }
		    }
    	}
    }

    /**
     * Forces the Reverb of Spectator independently of the room.
     */
    protected void forceReverb(int index) {
	    revName = House.presetNameByIndex(index);
	    reverb.setPreset(revName);
	    System.out.println("Manually forced reverb: " + House.presetNameByIndex(index));
    }

    /**
     * Gets the current reverb preset's name.
     */
    protected String getRevName() {
    	return revName;
    }
}