Update hello_audio.adoc

Update hello_audio.adoc

Changed tip text to Admonition.

= jMonkeyEngine 3 Tutorial (11) - Hello Audio
:author:
:revnumber:
:revdate: 2016/03/17 20:48
:keywords: sound, documentation, beginner, intro
:relfileprefix: ../../
:imagesdir: ../..
ifdef::env-github,env-browser[:outfilesuffix: .adoc]

Previous: <<jme3/beginner/hello_terrain#,Hello Terrain>>, Next: <<jme3/beginner/hello_effects#,Hello Effects>>

This tutorial explains how to add 3D sound to a game, and how to make sounds play together with events, such as clicking. You learn how to use an Audio Listener and Audio Nodes. You also make use of an Action Listener and a MouseButtonTrigger from the previous <<jme3/beginner/hello_input_system#,Hello Input>> tutorial to make a mouse click trigger a gun shot sound.

[TIP]
====
To use the example assets in a new jMonkeyEngine SDK project, right-click your project, select “Properties, go to “Libraries, press “Add Library and add the “jme3-test-data library.
====

== Sample Code

[source,java]
----
package jme3test.helloworld;

import com.jme3.app.SimpleApplication;
import com.jme3.audio.AudioNode;
import com.jme3.audio.AudioData.DataType;
import com.jme3.input.MouseInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.MouseButtonTrigger;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.scene.Geometry;
import com.jme3.scene.shape.Box;

/** Sample 11 - playing 3D audio. */
public class HelloAudio extends SimpleApplication {

  private AudioNode audio_gun;
  private AudioNode audio_nature;
  private Geometry player;

  public static void main(String[] args) {
    HelloAudio app = new HelloAudio();
    app.start();
  }

  @Override
  public void simpleInitApp() {
    flyCam.setMoveSpeed(40);

    /** just a blue box floating in space */
    Box box1 = new Box(1, 1, 1);
    player = new Geometry("Player", box1);
    Material mat1 = new Material(assetManager,"Common/MatDefs/Misc/Unshaded.j3md");
    mat1.setColor("Color", ColorRGBA.Blue);
    player.setMaterial(mat1);
    rootNode.attachChild(player);

    /** custom init methods, see below */
    initKeys();
    initAudio();
  }

  /** We create two audio nodes. */
  private void initAudio() {
    /* gun shot sound is to be triggered by a mouse click. */
    audio_gun = new AudioNode(assetManager, "Sound/Effects/Gun.wav", DataType.Buffer);
    audio_gun.setPositional(false);
    audio_gun.setLooping(false);
    audio_gun.setVolume(2);
    rootNode.attachChild(audio_gun);

    /* nature sound - keeps playing in a loop. */
    audio_nature = new AudioNode(assetManager, "Sound/Environment/Ocean Waves.ogg", DataType.Stream);
    audio_nature.setLooping(true);  // activate continuous playing
    audio_nature.setPositional(true);
    audio_nature.setVolume(3);
    rootNode.attachChild(audio_nature);
    audio_nature.play(); // play continuously!
  }

  /** Declaring "Shoot" action, mapping it to a trigger (mouse left click). */
  private void initKeys() {
    inputManager.addMapping("Shoot", new MouseButtonTrigger(MouseInput.BUTTON_LEFT));
    inputManager.addListener(actionListener, "Shoot");
  }

  /** Defining the "Shoot" action: Play a gun sound. */
  private ActionListener actionListener = new ActionListener() {
    @Override
    public void onAction(String name, boolean keyPressed, float tpf) {
      if (name.equals("Shoot") && !keyPressed) {
        audio_gun.playInstance(); // play each instance once!
      }
    }
  };

  /** Move the listener with the a camera - for 3D audio. */
  @Override
  public void simpleUpdate(float tpf) {
    listener.setLocation(cam.getLocation());
    listener.setRotation(cam.getRotation());
  }

}

----

When you run the sample, you should see a blue cube. You should hear a nature-like ambient sound. When you click, you hear a loud shot.

== Understanding the Code Sample

In the `initSimpleApp()` method, you create a simple blue cube geometry called `player` and attach it to the scene – this is just arbitrary sample content, so you see something when running the audio sample.

Let's have a closer look at `initAudio()` to learn how to use `AudioNode`s.

== AudioNodes

Adding sound to your game is quite simple: Save your audio files into your `assets/Sound` directory. JME3 supports both Ogg Vorbis (.ogg) and Wave (.wav) file formats.

For each sound, you create an AudioNode. You can use an AudioNode like any node in the JME scene graph, e.g. attach it to other Nodes. You create one node for a gunshot sound, and one node for a nature sound.

[source,java]
----

  private AudioNode audio_gun;
  private AudioNode audio_nature;

----

Look at the custom `initAudio()` method: Here you initialize the sound objects and set their parameters.

[source,Java]
----

audio_gun = new AudioNode(assetManager, "Sound/Effects/Gun.wav", DataType.Buffer);
    ...
audio_nature = new AudioNode(assetManager, "Sound/Environment/Nature.ogg", DataType.Stream);

----

These two lines create new sound nodes from the given audio files in the AssetManager. The `DataType.Buffer` flag means that you want to buffer these sounds before playing. (If you set this flag to `DataType.Stream`, the sound will be streamed, which makes sense for really long sounds.)

You want the gunshot sound to play _once_ (you don't want it to loop). You also specify its volume as gain factor (at 0, sound is muted, at 2, it is twice as loud, etc.).

[source,java]
----

    audio_gun.setPositional(false);
    audio_gun.setLooping(false);
    audio_gun.setVolume(2);
    rootNode.attachChild(audio_gun);

----

[IMPORTANT]
====
Note that setPositional(false) is pretty important when you use stereo sounds. Positional sounds must always be mono audio files, otherwise the engine will remind it to you with a crash.
====

The nature sound is different: You want it to loop _continuously_ as background sound. This is why you set looping to true, and immediately call the play() method on the node. You also choose to set its volume to 3.

[source,java]
----

    audio_nature.setLooping(true); // activate continuous playing
    ...
    audio_nature.setVolume(3);
    rootNode.attachChild(audio_nature);
    audio_nature.play(); // play continuously!
  }
----

Here you make audio_nature a positional sound that comes from a certain place. For that you give the node an explicit translation, in this example, you choose Vector3f.ZERO (which stands for the coordinates `0.0f,0.0f,0.0f`, the center of the scene.) Since jME supports 3D audio, you are now able to hear this sound coming from this particular location. Making the sound positional is optional. If you don't use these lines, the ambient sound comes from every direction.

[source,java]
----

    ...
    audio_nature.setPositional(true);
    audio_nature.setLocalTranslation(Vector3f.ZERO.clone());
    ...

----

[TIP]
====
Attach AudioNodes into the scene graph like all nodes, to make certain moving nodes stay up-to-date. If you don't attach them, they are still audible and you don't get an error message but 3D sound will not work as expected. AudioNodes can be attached directly to the root node or they can be attached inside a node that is moving through the scene and both the AudioNode and the 3d position of the sound it is generating will move accordingly.
====

[TIP]
====
playInstance always plays the sound from the position of the AudioNode so multiple gunshots from one gun (for example) can be generated this way, however if multiple guns are firing at once then an AudioNode is needed for each one.
====

== Triggering Sound

Let's have a closer look at `initKeys()`: As you learned in previous tutorials, you use the `inputManager` to respond to user input. Here you add a mapping for a left mouse button click, and name this new action `Shoot`.

[source,java]
----

  /** Declaring "Shoot" action, mapping it to a trigger (mouse left click). */
  private void initKeys() {
    inputManager.addMapping("Shoot", new MouseButtonTrigger(MouseInput.BUTTON_LEFT));
    inputManager.addListener(actionListener, "Shoot");
  }

----

Setting up the ActionListener should also be familiar from previous tutorials. You declare that, when the trigger (the mouse button) is pressed and released, you want to play a gun sound.

[source,java]
----

  /** Defining the "Shoot" action: Play a gun sound. */
  private ActionListener actionListener = new ActionListener() {
    @Override
    public void onAction(String name, boolean keyPressed, float tpf) {
      if (name.equals("Shoot") && !keyPressed) {
        audio_gun.playInstance(); // play each instance once!
      }
    }
  };
----

Since you want to be able to shoot fast repeatedly, so you do not want to wait for the previous gunshot sound to end before the next one can start. This is why you play this sound using the `playInstance()` method. This means that every click starts a new instance of the sound, so two instances can overlap. You set this sound not to loop, so each instance only plays once. As you would expect it of a gunshot.

== Ambient or Situational?

The two sounds are two different use cases:

*  A gunshot is situational. You want to play it only once, right when it is triggered.
**  This is why you `setLooping(false)`.

*  The nature sound is an ambient, background noise. You want it to start playing from the start, as long as the game runs.
**  This is why you `setLooping(true)`.

Now every sound knows whether it should loop or not.

Apart from the looping boolean, another difference is where `play().playInstance()` is called on those nodes:

*  You start playing the background nature sound right after you have created it, in the initAudio() method.
[source,java]
----
    audio_nature.play(); // play continuously!

----

*  The gunshot sound, however, is triggered situationally, once, only as part of the `Shoot` input action that you defined in the ActionListener.
[source,java]
----

  /** Defining the "Shoot" action: Play a gun sound. */
  private ActionListener actionListener = new ActionListener() {
    @Override
    public void onAction(String name, boolean keyPressed, float tpf) {
      if (name.equals("Shoot") && !keyPressed) {
        audio_gun.playInstance(); // play each instance once!
      }
    }
  };
----

== Buffered or Streaming?

As of 3.1-alpha2, the Enum in the AudioNode constructor defines whether the audio is buffered or streamed. For example:

[source,java]
----
audio_gunshot = new AudioNode(assetManager, "Sound/Effects/Gun.wav", DataType.Buffer); // buffered
...
audio_nature = new AudioNode(assetManager, "Sound/Environment/Nature.ogg", DataType.Stream); // streamed
----

Typically, you stream long sounds, and buffer short sounds.

+++<strike>Note that streamed sounds can not loop (i.e. setLooping will not work as you expect). Check the getStatus on the node and if it has stopped recreate the node.</strike>+++ (In 3.1-alpha2, this is wrong).
If you still run 3.0, the above is still the case aswell as a simple boolean will be used instead of `DataType`

== Play() or PlayInstance()?
[cols="2", options="header"]
|===

a|audio.play()
a|audio.playInstance()

a|Plays buffered sounds.
a|Plays buffered sounds.

a|Plays streamed sounds.
a|Cannot play streamed sounds.

a|The same sound cannot play twice at the same time.
a|The same sounds can play multiple times and overlap.

|===

== Your Ear in the Scene

To create a 3D audio effect, JME3 needs to know the position of the sound source, and the position of the ears of the player. The ears are represented by an 3D Audio Listener object. The `listener` object is a default object in a SimpleApplication.

In order to make the most of the 3D audio effect, you must use the `simpleUpdate()` method to move and rotate the listener (the player's ears) together with the camera (the player's eyes).

[source,java]
----

  public void simpleUpdate(float tpf) {
    listener.setLocation(cam.getLocation());
    listener.setRotation(cam.getRotation());
  }

----

If you don't do that, the results of 3D audio will be quite random.

== Global, Directional, Positional?

In this example, you defined the nature sound as coming from a certain position, but not the gunshot sound. This means your gunshot is global and can be heard everywhere with the same volume. JME3 also supports directional sounds which you can only hear from a certain direction.

It makes equal sense to make the gunshot positional, and let the ambient sound come from every direction. How do you decide which type of 3D sound to use from case to case?

*  In a game with moving enemies you may want to make the gun shot or footsteps positional sounds. In these cases you must move the AudioNode to the location of the enemy before `playInstance()`ing it. This way a player with stereo speakers hears from which direction the enemy is coming.
*  Similarly, you may have game levels where you want one background sound to play globally. In this case, you would make the AudioNode neither positional nor directional (set both to false).
*  If you want sound to be “absorbed by the walls and only broadcast in one direction, you would make this AudioNode directional. This tutorial does not discuss directional sounds, you can read about <<jme3/advanced/audio#,Advanced Audio>> here.

In short, you must choose in every situation whether it makes sense for a sound to be global, directional, or positional.

== Conclusion

You now know how to add the two most common types of sound to your game: Global sounds and positional sounds. You can play sounds in two ways: Either continuously in a loop, or situationally just once. You know the difference between buffering short sounds and streaming long sounds. You know the difference between playing overlapping sound instances, and playing unique sounds that cannot overlap with themselves. You also learned to use sound files that are in either .ogg or .wav format.

[TIP]
====
JME's Audio implementation also supports more advanced effects such as reverberation and Doppler effect. Use these “pro features to make audio sound different depending on whether it's in the hallway, in a cave, outdoors, or in a carpeted room. Find out more about environmental effects from the sample code included in the jme3test directory and from the advanced <<jme3/advanced/audio#,Audio>> docs.
====

Want some fire and explosions to go with your sounds? Read on to learn more about <<jme3/beginner/hello_effects#,effects>>.

'''

See also:

*   <<jme3/advanced/audio#,Audio>>

= jMonkeyEngine 3 Tutorial (11) - Hello Audio
:author:
:revnumber:
:revdate: 2016/03/17 20:48
:keywords: sound, documentation, beginner, intro
:relfileprefix: ../../
:imagesdir: ../..
ifdef::env-github,env-browser[:outfilesuffix: .adoc]

Previous: <<jme3/beginner/hello_terrain#,Hello Terrain>>, Next: <<jme3/beginner/hello_effects#,Hello Effects>>

This tutorial explains how to add 3D sound to a game, and how to make sounds play together with events, such as clicking. You learn how to use an Audio Listener and Audio Nodes. You also make use of an Action Listener and a MouseButtonTrigger from the previous <<jme3/beginner/hello_input_system#,Hello Input>> tutorial to make a mouse click trigger a gun shot sound.

[TIP]
====
To use the example assets in a new jMonkeyEngine SDK project, right-click your project, select “Properties, go to “Libraries, press “Add Library and add the “jme3-test-data library.
====

== Sample Code

[source,java]
----
package jme3test.helloworld;

import com.jme3.app.SimpleApplication;
import com.jme3.audio.AudioNode;
import com.jme3.audio.AudioData.DataType;
import com.jme3.input.MouseInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.MouseButtonTrigger;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.scene.Geometry;
import com.jme3.scene.shape.Box;

/** Sample 11 - playing 3D audio. */
public class HelloAudio extends SimpleApplication {

  private AudioNode audio_gun;
  private AudioNode audio_nature;
  private Geometry player;

  public static void main(String[] args) {
    HelloAudio app = new HelloAudio();
    app.start();
  }

  @Override
  public void simpleInitApp() {
    flyCam.setMoveSpeed(40);

    /** just a blue box floating in space */
    Box box1 = new Box(1, 1, 1);
    player = new Geometry("Player", box1);
    Material mat1 = new Material(assetManager,"Common/MatDefs/Misc/Unshaded.j3md");
    mat1.setColor("Color", ColorRGBA.Blue);
    player.setMaterial(mat1);
    rootNode.attachChild(player);

    /** custom init methods, see below */
    initKeys();
    initAudio();
  }

  /** We create two audio nodes. */
  private void initAudio() {
    /* gun shot sound is to be triggered by a mouse click. */
    audio_gun = new AudioNode(assetManager, "Sound/Effects/Gun.wav", DataType.Buffer);
    audio_gun.setPositional(false);
    audio_gun.setLooping(false);
    audio_gun.setVolume(2);
    rootNode.attachChild(audio_gun);

    /* nature sound - keeps playing in a loop. */
    audio_nature = new AudioNode(assetManager, "Sound/Environment/Ocean Waves.ogg", DataType.Stream);
    audio_nature.setLooping(true);  // activate continuous playing
    audio_nature.setPositional(true);
    audio_nature.setVolume(3);
    rootNode.attachChild(audio_nature);
    audio_nature.play(); // play continuously!
  }

  /** Declaring "Shoot" action, mapping it to a trigger (mouse left click). */
  private void initKeys() {
    inputManager.addMapping("Shoot", new MouseButtonTrigger(MouseInput.BUTTON_LEFT));
    inputManager.addListener(actionListener, "Shoot");
  }

  /** Defining the "Shoot" action: Play a gun sound. */
  private ActionListener actionListener = new ActionListener() {
    @Override
    public void onAction(String name, boolean keyPressed, float tpf) {
      if (name.equals("Shoot") && !keyPressed) {
        audio_gun.playInstance(); // play each instance once!
      }
    }
  };

  /** Move the listener with the a camera - for 3D audio. */
  @Override
  public void simpleUpdate(float tpf) {
    listener.setLocation(cam.getLocation());
    listener.setRotation(cam.getRotation());
  }

}

----

When you run the sample, you should see a blue cube. You should hear a nature-like ambient sound. When you click, you hear a loud shot.

== Understanding the Code Sample

In the `initSimpleApp()` method, you create a simple blue cube geometry called `player` and attach it to the scene – this is just arbitrary sample content, so you see something when running the audio sample.

Let's have a closer look at `initAudio()` to learn how to use `AudioNode`s.

== AudioNodes

Adding sound to your game is quite simple: Save your audio files into your `assets/Sound` directory. JME3 supports both Ogg Vorbis (.ogg) and Wave (.wav) file formats.

For each sound, you create an AudioNode. You can use an AudioNode like any node in the JME scene graph, e.g. attach it to other Nodes. You create one node for a gunshot sound, and one node for a nature sound.

[source,java]
----

  private AudioNode audio_gun;
  private AudioNode audio_nature;

----

Look at the custom `initAudio()` method: Here you initialize the sound objects and set their parameters.

[source,Java]
----

audio_gun = new AudioNode(assetManager, "Sound/Effects/Gun.wav", DataType.Buffer);
    ...
audio_nature = new AudioNode(assetManager, "Sound/Environment/Nature.ogg", DataType.Stream);

----

These two lines create new sound nodes from the given audio files in the AssetManager. The `DataType.Buffer` flag means that you want to buffer these sounds before playing. (If you set this flag to `DataType.Stream`, the sound will be streamed, which makes sense for really long sounds.)

You want the gunshot sound to play _once_ (you don't want it to loop). You also specify its volume as gain factor (at 0, sound is muted, at 2, it is twice as loud, etc.).

[source,java]
----

    audio_gun.setPositional(false);
    audio_gun.setLooping(false);
    audio_gun.setVolume(2);
    rootNode.attachChild(audio_gun);

----

[IMPORTANT]
====
Note that setPositional(false) is pretty important when you use stereo sounds. Positional sounds must always be mono audio files, otherwise the engine will remind it to you with a crash.
====

The nature sound is different: You want it to loop _continuously_ as background sound. This is why you set looping to true, and immediately call the play() method on the node. You also choose to set its volume to 3.

[source,java]
----

    audio_nature.setLooping(true); // activate continuous playing
    ...
    audio_nature.setVolume(3);
    rootNode.attachChild(audio_nature);
    audio_nature.play(); // play continuously!
  }
----

Here you make audio_nature a positional sound that comes from a certain place. For that you give the node an explicit translation, in this example, you choose Vector3f.ZERO (which stands for the coordinates `0.0f,0.0f,0.0f`, the center of the scene.) Since jME supports 3D audio, you are now able to hear this sound coming from this particular location. Making the sound positional is optional. If you don't use these lines, the ambient sound comes from every direction.

[source,java]
----

    ...
    audio_nature.setPositional(true);
    audio_nature.setLocalTranslation(Vector3f.ZERO.clone());
    ...

----

*Tip:* Attach AudioNodes into the scene graph like all nodes, to make certain moving nodes stay up-to-date. If you don't attach them, they are still audible and you don't get an error message but 3D sound will not work as expected. AudioNodes can be attached directly to the root node or they can be attached inside a node that is moving through the scene and both the AudioNode and the 3d position of the sound it is generating will move accordingly.

*Tip:* playInstance always plays the sound from the position of the AudioNode so multiple gunshots from one gun (for example) can be generated this way, however if multiple guns are firing at once then an AudioNode is needed for each one.

== Triggering Sound

Let's have a closer look at `initKeys()`: As you learned in previous tutorials, you use the `inputManager` to respond to user input. Here you add a mapping for a left mouse button click, and name this new action `Shoot`.

[source,java]
----

  /** Declaring "Shoot" action, mapping it to a trigger (mouse left click). */
  private void initKeys() {
    inputManager.addMapping("Shoot", new MouseButtonTrigger(MouseInput.BUTTON_LEFT));
    inputManager.addListener(actionListener, "Shoot");
  }

----

Setting up the ActionListener should also be familiar from previous tutorials. You declare that, when the trigger (the mouse button) is pressed and released, you want to play a gun sound.

[source,java]
----

  /** Defining the "Shoot" action: Play a gun sound. */
  private ActionListener actionListener = new ActionListener() {
    @Override
    public void onAction(String name, boolean keyPressed, float tpf) {
      if (name.equals("Shoot") && !keyPressed) {
        audio_gun.playInstance(); // play each instance once!
      }
    }
  };
----

Since you want to be able to shoot fast repeatedly, so you do not want to wait for the previous gunshot sound to end before the next one can start. This is why you play this sound using the `playInstance()` method. This means that every click starts a new instance of the sound, so two instances can overlap. You set this sound not to loop, so each instance only plays once. As you would expect it of a gunshot.

== Ambient or Situational?

The two sounds are two different use cases:

*  A gunshot is situational. You want to play it only once, right when it is triggered.
**  This is why you `setLooping(false)`.

*  The nature sound is an ambient, background noise. You want it to start playing from the start, as long as the game runs.
**  This is why you `setLooping(true)`.

Now every sound knows whether it should loop or not.

Apart from the looping boolean, another difference is where `play().playInstance()` is called on those nodes:

*  You start playing the background nature sound right after you have created it, in the initAudio() method.
[source,java]
----
    audio_nature.play(); // play continuously!

----

*  The gunshot sound, however, is triggered situationally, once, only as part of the `Shoot` input action that you defined in the ActionListener.
[source,java]
----

  /** Defining the "Shoot" action: Play a gun sound. */
  private ActionListener actionListener = new ActionListener() {
    @Override
    public void onAction(String name, boolean keyPressed, float tpf) {
      if (name.equals("Shoot") && !keyPressed) {
        audio_gun.playInstance(); // play each instance once!
      }
    }
  };
----

== Buffered or Streaming?

As of 3.1-alpha2, the Enum in the AudioNode constructor defines whether the audio is buffered or streamed. For example:

[source,java]
----
audio_gunshot = new AudioNode(assetManager, "Sound/Effects/Gun.wav", DataType.Buffer); // buffered
...
audio_nature = new AudioNode(assetManager, "Sound/Environment/Nature.ogg", DataType.Stream); // streamed
----

Typically, you stream long sounds, and buffer short sounds.

+++<strike>Note that streamed sounds can not loop (i.e. setLooping will not work as you expect). Check the getStatus on the node and if it has stopped recreate the node.</strike>+++ (In 3.1-alpha2, this is wrong).
If you still run 3.0, the above is still the case aswell as a simple boolean will be used instead of `DataType`

== Play() or PlayInstance()?
[cols="2", options="header"]
|===

a|audio.play()
a|audio.playInstance()

a|Plays buffered sounds.
a|Plays buffered sounds.

a|Plays streamed sounds.
a|Cannot play streamed sounds.

a|The same sound cannot play twice at the same time.
a|The same sounds can play multiple times and overlap.

|===

== Your Ear in the Scene

To create a 3D audio effect, JME3 needs to know the position of the sound source, and the position of the ears of the player. The ears are represented by an 3D Audio Listener object. The `listener` object is a default object in a SimpleApplication.

In order to make the most of the 3D audio effect, you must use the `simpleUpdate()` method to move and rotate the listener (the player's ears) together with the camera (the player's eyes).

[source,java]
----

  public void simpleUpdate(float tpf) {
    listener.setLocation(cam.getLocation());
    listener.setRotation(cam.getRotation());
  }

----

If you don't do that, the results of 3D audio will be quite random.

== Global, Directional, Positional?

In this example, you defined the nature sound as coming from a certain position, but not the gunshot sound. This means your gunshot is global and can be heard everywhere with the same volume. JME3 also supports directional sounds which you can only hear from a certain direction.

It makes equal sense to make the gunshot positional, and let the ambient sound come from every direction. How do you decide which type of 3D sound to use from case to case?

*  In a game with moving enemies you may want to make the gun shot or footsteps positional sounds. In these cases you must move the AudioNode to the location of the enemy before `playInstance()`ing it. This way a player with stereo speakers hears from which direction the enemy is coming.
*  Similarly, you may have game levels where you want one background sound to play globally. In this case, you would make the AudioNode neither positional nor directional (set both to false).
*  If you want sound to be “absorbed by the walls and only broadcast in one direction, you would make this AudioNode directional. This tutorial does not discuss directional sounds, you can read about <<jme3/advanced/audio#,Advanced Audio>> here.

In short, you must choose in every situation whether it makes sense for a sound to be global, directional, or positional.

== Conclusion

You now know how to add the two most common types of sound to your game: Global sounds and positional sounds. You can play sounds in two ways: Either continuously in a loop, or situationally just once. You know the difference between buffering short sounds and streaming long sounds. You know the difference between playing overlapping sound instances, and playing unique sounds that cannot overlap with themselves. You also learned to use sound files that are in either .ogg or .wav format.

*Tip:* JME's Audio implementation also supports more advanced effects such as reverberation and Doppler effect. Use these “pro features to make audio sound different depending on whether it's in the hallway, in a cave, outdoors, or in a carpeted room. Find out more about environmental effects from the sample code included in the jme3test directory and from the advanced <<jme3/advanced/audio#,Audio>> docs.

Want some fire and explosions to go with your sounds? Read on to learn more about <<jme3/beginner/hello_effects#,effects>>.

'''

See also:

*   <<jme3/advanced/audio#,Audio>>


Bug 1764353 Remove namespace and other minor updates

// Module included in the following assemblies:
//
// * openshift_images/configuring_samples_operator.adoc

[id="samples-operator-crd{context}"]
= Accessing the Samples Operator configuration

You can configure the Samples Operator by editing the file with the provided
parameters.

.Prerequisites

* Install the OpenShift Command-line Interface (CLI), commonly known as `oc`.

.Procedure

*  Access the Samples Operator configuration:
+
----
$ oc get configs.samples.operator.openshift.io/cluster -o yaml
----
+
The Samples Operator configuration resembles the following example:
+
[source,yaml]
----
apiVersion: samples.operator.openshift.io/v1
kind: Config
projectName: cluster-samples-operator
...
----

// Module included in the following assemblies:
//
// * openshift_images/configuring_samples_operator.adoc

[id="samples-operator-crd{context}"]
= Accessing the Samples Operator file

You can configure the Samples Operator by editing the file with the provided
parameters.

.Procedure

*  Access the Samples Operator file:
+
----
# oc get configs.samples.operator.openshift.io -n openshift-cluster-samples-operator
----
+
The following is an example of the Samples Operator file:
+
[source,yaml]
----
apiVersion: samples.operator.openshift.io/v1
kind: SamplesResource
projectName: cluster-samples-operator
...
----


fix(process): fix link in TOC

fix(process): fix link in TOC

// build_options:
Java Performance Tuning - Performance Testing Process
=====================================================
Arnauld Van Muysewinkel <avm@pendragon.be>
v0.1, 28-Oct-2015: Initial version
:backend: slidy
//:theme: volnitsky
:data-uri:
:copyright: Creative-Commons-Zero (Arnauld Van Muysewinkel)
:icons:

Content
-------

* <<_rtri_documentation,Documentation>>
* <<_rtri_iteration,Iteration>>
* <<_rtri_process_smals,Process for SIC@Smals>>
** Steps
// (p16)
//** Plan
// (p18,19),
** Mission Sheet

_(link:../0-extra/1-training_plan.html#_presentations[back to course plan])_

▹ Documentation
--------------------

* Architecture document of the UoT
* Test plan:
** goals
** scope (boundaries of the UoT)
** resources required (! including key people for running the test or for solving issues)
** schedulling
* Requirements: performance goals
* Test data: input data for the test scenario (! quantity and randomization)
* Test protocol: process, scenarios, load profiles...
* Test report:
** all results (measures and calculations)
** conclusions

▹ Iteration
----------------

[WARNING]
=====
* Fix > *one* < issue at a time! +
Not doing so exposes you to major problems.
* Iterate only if it's worth the price +
(i.e. does the client want to pay for the next performance improvement?)
=====

▹ Process @ Smals
----------------------

* [Project] Prepare Mission Sheet (V1)
* Send V1 to SIC -> schedule KOM
* [Project+SIC] KOM
** Agree on the high level design of the test protocol (scenarios, load profiles...)
* LOOP
** [Project] Complete Mission Sheet (V2)
*** Final artifacts
*** Complete set of input data
*** Complete set of users (attributes...)
** [SIC] Detailed test protocol
*** [Project] Support
** [SIC] Prepare test environment
*** [Project] Support
*** [Middleware] Support
** [SIC] Prepare test harness (jmeter scripts, SOAP clients, jenkins configuration...)
*** [Project] Support
** [SIC] Run test
** [SIC] Analyse results
** [SIC+Project] discuss results
** ITERATE
* [SIC] Prepare report
** [Project] Review report
* [SIC] Publish report

Mission Sheet
-------------

See template in https://github.com/arnauldvm/jpt-course/tree/master/data/docs[data/docs] in git repo

Protocol
--------

See legacy reference protocol in https://github.com/arnauldvm/jpt-course/tree/master/data/docs[data/docs] in git repo

That's all folks!
-----------------

[cols="^",grid="none",frame="none"]
|=====
|image:../thats-all-folks.png[link="#(1)"]
|=====

// build_options:
Java Performance Tuning - Performance Testing Process
=====================================================
Arnauld Van Muysewinkel <avm@pendragon.be>
v0.1, 28-Oct-2015: Initial version
:backend: slidy
//:theme: volnitsky
:data-uri:
:copyright: Creative-Commons-Zero (Arnauld Van Muysewinkel)
:icons:

Content
-------

* <<_rtri_documentation,Documentation>>
* <<_rtri_process,Process>>
* <<_rtri_process_smals,Process for SIC@Smals>>
** Steps
// (p16)
//** Plan
// (p18,19),
** Mission Sheet

_(link:../0-extra/1-training_plan.html#_presentations[back to course plan])_

▹ Documentation
--------------------

* Architecture document of the UoT
* Test plan:
** goals
** scope (boundaries of the UoT)
** resources required (! including key people for running the test or for solving issues)
** schedulling
* Requirements: performance goals
* Test data: input data for the test scenario (! quantity and randomization)
* Test protocol: process, scenarios, load profiles...
* Test report:
** all results (measures and calculations)
** conclusions

▹ Iteration
----------------

[WARNING]
=====
* Fix > *one* < issue at a time! +
Not doing so exposes you to major problems.
* Iterate only if it's worth the price +
(i.e. does the client want to pay for the next performance improvement?)
=====

▹ Process @ Smals
----------------------

* [Project] Prepare Mission Sheet (V1)
* Send V1 to SIC -> schedule KOM
* [Project+SIC] KOM
** Agree on the high level design of the test protocol (scenarios, load profiles...)
* LOOP
** [Project] Complete Mission Sheet (V2)
*** Final artifacts
*** Complete set of input data
*** Complete set of users (attributes...)
** [SIC] Detailed test protocol
*** [Project] Support
** [SIC] Prepare test environment
*** [Project] Support
*** [Middleware] Support
** [SIC] Prepare test harness (jmeter scripts, SOAP clients, jenkins configuration...)
*** [Project] Support
** [SIC] Run test
** [SIC] Analyse results
** [SIC+Project] discuss results
** ITERATE
* [SIC] Prepare report
** [Project] Review report
* [SIC] Publish report

Mission Sheet
-------------

See template in https://github.com/arnauldvm/jpt-course/tree/master/data/docs[data/docs] in git repo

Protocol
--------

See legacy reference protocol in https://github.com/arnauldvm/jpt-course/tree/master/data/docs[data/docs] in git repo

That's all folks!
-----------------

[cols="^",grid="none",frame="none"]
|=====
|image:../thats-all-folks.png[link="#(1)"]
|=====


Fix asciidoc

Fix asciidoc


Add a snippet to the manual showing how to specify proxy settings for BrowserStack tunnel.

= Cloud browser testing

When you want to perform web testing on multiple browsers and operating systems, it can be quite complicated to maintain machines for each of the target environments.
There are a few companies that provide "remote web browsers as a service", making it easy to do this sort of matrix testing without having to maintain the multiple browser installations yourself.
Geb provides easy integration with two such services, link:https://saucelabs.com/[SauceLabs] and link:http://www.browserstack.com/[BrowserStack].
This integration includes two parts: assistance with creating a driver in `GebConfig.groovy` and a Gradle plugin.

== Creating a driver

For both SauceLabs and BrowserStack, a special driver factory is provided that, given a browser specification as well as an username and access key, creates an instance of `RemoteWebDriver` configured
to use a browser in the cloud.
Examples of typical usage in `GebConfig.groovy` are included below.
They will configure Geb to run in SauceLabs/BrowserStack if the appropriate system property is set, and if not it will use whatever driver that is configured.
This is useful if you want to run the code in a local browser for development.
In theory you could use any system property to pass the browser specification but `geb.saucelabs.browser`/`geb.browserstack.browser` are also used by the Geb Gradle plugins, so it's a good idea to
stick with those property names.

The first parameter passed to the `create()` method is a ”browser specification“ and it should be a list of required browser capabilities in Java properties file format:

----
browserName=«browser name as per values of fields in org.openqa.selenium.remote.BrowserType»
platform=«platform as per enum item names in org.openqa.selenium.Platform»
version=«version»
----

Assuming you're using the following snippet in your `GebConfig.groovy` to execute your code via SauceLabs with Firefox 19 on Linux, you would set the `geb.saucelabs.browser` system property to:

----
browserName=firefox
platform=LINUX
version=19
----

and to execute it with IE 9 on Vista to:

----
browserName=internet explorer
platform=VISTA
version=9
----

Some browsers like Chrome automatically update to the latest version; for these browsers you don't need to specify the version as there's only one, and you would use something like:

----
browserName=chrome
platform=MAC
----

as the ”browser specification“. For a full list of available browsers, versions and operating systems refer to your cloud provider's documentation:

* link:https://saucelabs.com/docs/platforms/webdriver[SauceLabs platform list]
* link:http://www.browserstack.com/list-of-browsers-and-platforms?product=automate[BrowserStack Browsers and Platforms list]

Please note that Geb Gradle plugins can set the `geb.saucelabs.browser`/`geb.browserstack.browser` system properties for you using the aforementioned format.

Following the browser specification are the username and access key used to identify your account with the cloud provider.
The example uses two environment variables to access this information.
This is usually the easiest way of passing something secret to your build in open CI services like link:https://drone.io/[drone.io] or link:https://travis-ci.org/[Travis CI] if your code is public, but you can
use other mechanisms if desired.

You can optionally pass additional configuration settings by providing a Map to the `create()` method as the last parameter.
The configuration options available are described in your cloud provider's documentation:

* link:https://saucelabs.com/docs/additional-config[SauceLabs additional config]
* link:http://www.browserstack.com/automate/capabilities[BrowserStack Capabilities]

Finally, there is also link:api/geb/driver/CloudDriverFactory.html#create(java.lang.String,%20java.lang.String,%20Map%3CString,%20Object%3E)[an overloaded version of `create()` method] available that
doesn't take a string specification and allows you to simply specify all the required capabilities using a map.
This method might be useful if you just want to use the factory, but don't need the build level parametrization.

[[sauce-labs-driver-factory]]
=== `SauceLabsDriverFactory`

The following is an example of utilizing `SauceLabsDriverFactory` in `GebConfig.groovy` to configure a driver that will use a browser provided in the SauceLabs cloud.

[source,groovy]
----
def sauceLabsBrowser = System.getProperty("geb.saucelabs.browser")
if (sauceLabsBrowser) {
    driver = {
       def username = System.getenv("GEB_SAUCE_LABS_USER")
       assert username
       def accessKey = System.getenv("GEB_SAUCE_LABS_ACCESS_PASSWORD")
       assert accessKey
       new SauceLabsDriverFactory().create(sauceLabsBrowser, username, accessKey)
    }
}
----

[[browser-stack-driver-factory]]
=== `BrowserStackDriverFactory`

The following is an example of utilizing `BrowserStackDriverFactory` in `GebConfig.groovy` to configure a driver that will use a browser provided in the BrowserStack cloud.

[source,groovy]
----
def browserStackBrowser = System.getProperty("geb.browserstack.browser")
if (browserStackBrowser) {
    driver = {
       def username = System.getenv("GEB_BROWSERSTACK_USERNAME")
       assert username
       def accessKey = System.getenv("GEB_BROWSERSTACK_AUTHKEY")
       assert accessKey
       new BrowserStackDriverFactory().create(browserStackBrowser, username, accessKey)
    }
}
----

If using `localIdentifier` support:

[source,groovy]
----
def browserStackBrowser = System.getProperty("geb.browserstack.browser")
if (browserStackBrowser) {
    driver = {
       def username = System.getenv("GEB_BROWSERSTACK_USERNAME")
       assert username
       def accessKey = System.getenv("GEB_BROWSERSTACK_AUTHKEY")
       assert accessKey
       def localId = System.getenv("GEB_BROWSERSTACK_LOCALID")
       assert localId
       new BrowserStackDriverFactory().create(browserStackBrowser, username, accessKey, localId)
    }
}
----

== Gradle plugins

For both SauceLabs and BrowserStack, Geb provides a Gradle plugin which simplifies declaring the account and browsers that are desired, as well as configuring a tunnel to allow the cloud provider to
access local applications.
These plugins allow easily creating multiple `Test` tasks that will have the appropriate `geb.PROVIDER.browser` property set (where _PROVIDER_ is either `saucelabs` or `browserstack`).
The value of that property can be then passed in configuration file to <<sauce-labs-driver-factory>>/<<browser-stack-driver-factory>> as the ”browser specification“.
Examples of typical usage are included below.

=== geb-saucelabs plugin

Following is an example of using the geb-saucelabs Gradle plugin.

[source,groovy,subs="attributes,verbatim"]
----
import geb.gradle.saucelabs.SauceAccount

apply plugin: "geb-saucelabs" //<1>

buildscript { //<2>
    repositories {
       mavenCentral()
    }
    dependencies {
       classpath 'org.gebish:geb-gradle:{geb-version}'
    }
}

repositories { //<3>
    maven { url "http://repository-saucelabs.forge.cloudbees.com/release" }
}

dependencies { //<4>
    sauceConnect "com.saucelabs:ci-sauce:1.81"
}

sauceLabs {
    browsers { //<5>
       firefox_linux_19
       chrome_mac
       delegate."internet explorer_vista_9"
       nexus4 { //<6>
         capabilities(
            browserName: "android",
            platform: "Linux",
            version: "4.4",
            deviceName: "LG Nexus 4"
         )
       }
    }
    task { //<7>
       testClassesDir = test.testClassesDir
       testSrcDirs = test.testSrcDirs
       classpath = test.classpath
    }
    account { //<8>
       username = System.getenv(SauceAccount.USER_ENV_VAR)
       accessKey = System.getenv(SauceAccount.ACCESS_KEY_ENV_VAR)
    }
    connect { //<9>
       port = 4444 //<10>
       additionalOptions = ['--proxy', 'proxy.example.com:8080'] //<11>
    }
}
----
<1> Apply the plugin to the build.
<2> Specify how to resolve the plugin.
<3> Declare a repository for resolving SauceConnect.
<4> Declare version of SauceConnect to be used as part of the `sauceConnect` configuration. This will be used by tasks that open a {sauce-connect} tunnel before
running the generated test tasks which means that the browsers in the cloud will have localhost pointing at the machine running the build.
<5> Declare that tests should run in 3 different browsers using the shorthand syntax; this will generate the following `Test` tasks: `firefoxLinux19Test`, `chromeMacTest` and
`internet explorerVista9Test`.
<6> Explicitly specify the required browser capabilities if the shorthand syntax doesn't allow you to express all needed capabilities; the example will generate a `Test` task named `nexus4Test`.
<7> Configure all of the generated test tasks; for each of them the closure is run with delegate set to a test task being configured.
<8> Pass credentials for {sauce-connect}.
<9> Additionally configure {sauce-connect} if desired.
<10> Override the port used by SauceConnect, defaults to 4445.
<11> Pass additional link:https://docs.saucelabs.com/reference/sauce-connect/#command-line-options[command line options] to SauceConnect.

[TIP]
====
You can use `allSauceLabsTests` task that will depend on all of the generated test tasks to run all of them during a build.
====

=== geb-browserstack plugin

Following is an example of using the geb-browserstack Gradle plugin.

[source,groovy,subs="attributes,verbatim"]
----
import geb.gradle.browserstack.BrowserStackAccount

apply plugin: "geb-browserstack" //<1>

buildscript { //<2>
    repositories {
       mavenCentral()
    }
    dependencies {
       classpath 'org.gebish:geb-gradle:{geb-version}'
    }
}

browserStack {
    application 'http://localhost:8080' //<3>
    forceLocal = true //<4>
    browsers { //<5>
       firefox_mac_19
       chrome_mac
       delegate."internet explorer_windows_9"
       nexus4 { //<6>
         capabilities browserName: "android", platform: "ANDROID", device: "Google Nexus 4"
       }
    }
    task { //<7>
       testClassesDir = test.testClassesDir
       testSrcDirs = test.testSrcDirs
       classpath = test.classpath
    }
    account { //<8>
       username = System.getenv(BrowserStackAccount.USER_ENV_VAR)
       accessKey = System.getenv(BrowserStackAccount.ACCESS_KEY_ENV_VAR)
    }
}
----
<1> Apply the plugin to the build.
<2> Specify how to resolve the plugin.
<3> Specify which urls the BrowserStack Tunnel should be able to access.
Multiple applications can be specified.
If no applications are specified, the tunnel will not be restricted to particular URLs.
<4> Configure BrowserStack tunnel to route all traffic via the local machine.
This configuration property controls the `-forcelocal` flag and the default value for it is `false`.
<5> Declare that tests should run in 3 different browsers using the shorthand syntax; this will generate the following `Test` tasks: `firefoxLinux19Test`, `chromeMacTest` and
`internet explorerVista9Test`.
<6> Explicitly specify the required browser capabilities if the shorthand syntax doesn't allow you to express all needed capabilities; the example will generate a `Test` task named `nexus4Test`.
<7> Configure all of the generated test tasks; for each of them the closure is run with delegate set to a test task being configured.
<8> Pass credentials for BrowserStack.

It's also possible to specify location and credentials for the proxy to be used with the BrowserStack Tunnel:

[source,groovy,subs="attributes,verbatim"]
----
browserStack {
    account {
        proxyHost = '127.0.0.1'
        proxyPort = '8080'
        proxyUser = 'user'
        proxyPass = 'secret'
    }
}
----

[TIP]
====
You can use `allBrowserStackTests` task that will depend on all of the generated test tasks to run all of them during a build.
====

= Cloud browser testing

When you want to perform web testing on multiple browsers and operating systems, it can be quite complicated to maintain machines for each of the target environments.
There are a few companies that provide "remote web browsers as a service", making it easy to do this sort of matrix testing without having to maintain the multiple browser installations yourself.
Geb provides easy integration with two such services, link:https://saucelabs.com/[SauceLabs] and link:http://www.browserstack.com/[BrowserStack].
This integration includes two parts: assistance with creating a driver in `GebConfig.groovy` and a Gradle plugin.

== Creating a driver

For both SauceLabs and BrowserStack, a special driver factory is provided that, given a browser specification as well as an username and access key, creates an instance of `RemoteWebDriver` configured
to use a browser in the cloud.
Examples of typical usage in `GebConfig.groovy` are included below.
They will configure Geb to run in SauceLabs/BrowserStack if the appropriate system property is set, and if not it will use whatever driver that is configured.
This is useful if you want to run the code in a local browser for development.
In theory you could use any system property to pass the browser specification but `geb.saucelabs.browser`/`geb.browserstack.browser` are also used by the Geb Gradle plugins, so it's a good idea to
stick with those property names.

The first parameter passed to the `create()` method is a ”browser specification“ and it should be a list of required browser capabilities in Java properties file format:

----
browserName=«browser name as per values of fields in org.openqa.selenium.remote.BrowserType»
platform=«platform as per enum item names in org.openqa.selenium.Platform»
version=«version»
----

Assuming you're using the following snippet in your `GebConfig.groovy` to execute your code via SauceLabs with Firefox 19 on Linux, you would set the `geb.saucelabs.browser` system property to:

----
browserName=firefox
platform=LINUX
version=19
----

and to execute it with IE 9 on Vista to:

----
browserName=internet explorer
platform=VISTA
version=9
----

Some browsers like Chrome automatically update to the latest version; for these browsers you don't need to specify the version as there's only one, and you would use something like:

----
browserName=chrome
platform=MAC
----

as the ”browser specification“. For a full list of available browsers, versions and operating systems refer to your cloud provider's documentation:

* link:https://saucelabs.com/docs/platforms/webdriver[SauceLabs platform list]
* link:http://www.browserstack.com/list-of-browsers-and-platforms?product=automate[BrowserStack Browsers and Platforms list]

Please note that Geb Gradle plugins can set the `geb.saucelabs.browser`/`geb.browserstack.browser` system properties for you using the aforementioned format.

Following the browser specification are the username and access key used to identify your account with the cloud provider.
The example uses two environment variables to access this information.
This is usually the easiest way of passing something secret to your build in open CI services like link:https://drone.io/[drone.io] or link:https://travis-ci.org/[Travis CI] if your code is public, but you can
use other mechanisms if desired.

You can optionally pass additional configuration settings by providing a Map to the `create()` method as the last parameter.
The configuration options available are described in your cloud provider's documentation:

* link:https://saucelabs.com/docs/additional-config[SauceLabs additional config]
* link:http://www.browserstack.com/automate/capabilities[BrowserStack Capabilities]

Finally, there is also link:api/geb/driver/CloudDriverFactory.html#create(java.lang.String,%20java.lang.String,%20Map%3CString,%20Object%3E)[an overloaded version of `create()` method] available that
doesn't take a string specification and allows you to simply specify all the required capabilities using a map.
This method might be useful if you just want to use the factory, but don't need the build level parametrization.

[[sauce-labs-driver-factory]]
=== `SauceLabsDriverFactory`

The following is an example of utilizing `SauceLabsDriverFactory` in `GebConfig.groovy` to configure a driver that will use a browser provided in the SauceLabs cloud.

[source,groovy]
----
def sauceLabsBrowser = System.getProperty("geb.saucelabs.browser")
if (sauceLabsBrowser) {
    driver = {
       def username = System.getenv("GEB_SAUCE_LABS_USER")
       assert username
       def accessKey = System.getenv("GEB_SAUCE_LABS_ACCESS_PASSWORD")
       assert accessKey
       new SauceLabsDriverFactory().create(sauceLabsBrowser, username, accessKey)
    }
}
----

[[browser-stack-driver-factory]]
=== `BrowserStackDriverFactory`

The following is an example of utilizing `BrowserStackDriverFactory` in `GebConfig.groovy` to configure a driver that will use a browser provided in the BrowserStack cloud.

[source,groovy]
----
def browserStackBrowser = System.getProperty("geb.browserstack.browser")
if (browserStackBrowser) {
    driver = {
       def username = System.getenv("GEB_BROWSERSTACK_USERNAME")
       assert username
       def accessKey = System.getenv("GEB_BROWSERSTACK_AUTHKEY")
       assert accessKey
       new BrowserStackDriverFactory().create(browserStackBrowser, username, accessKey)
    }
}
----

If using `localIdentifier` support:

[source,groovy]
----
def browserStackBrowser = System.getProperty("geb.browserstack.browser")
if (browserStackBrowser) {
    driver = {
       def username = System.getenv("GEB_BROWSERSTACK_USERNAME")
       assert username
       def accessKey = System.getenv("GEB_BROWSERSTACK_AUTHKEY")
       assert accessKey
       def localId = System.getenv("GEB_BROWSERSTACK_LOCALID")
       assert localId
       new BrowserStackDriverFactory().create(browserStackBrowser, username, accessKey, localId)
    }
}
----

== Gradle plugins

For both SauceLabs and BrowserStack, Geb provides a Gradle plugin which simplifies declaring the account and browsers that are desired, as well as configuring a tunnel to allow the cloud provider to
access local applications.
These plugins allow easily creating multiple `Test` tasks that will have the appropriate `geb.PROVIDER.browser` property set (where _PROVIDER_ is either `saucelabs` or `browserstack`).
The value of that property can be then passed in configuration file to <<sauce-labs-driver-factory>>/<<browser-stack-driver-factory>> as the ”browser specification“.
Examples of typical usage are included below.

=== geb-saucelabs plugin

Following is an example of using the geb-saucelabs Gradle plugin.

[source,groovy,subs="attributes,verbatim"]
----
import geb.gradle.saucelabs.SauceAccount

apply plugin: "geb-saucelabs" //<1>

buildscript { //<2>
    repositories {
       mavenCentral()
    }
    dependencies {
       classpath 'org.gebish:geb-gradle:{geb-version}'
    }
}

repositories { //<3>
    maven { url "http://repository-saucelabs.forge.cloudbees.com/release" }
}

dependencies { //<4>
    sauceConnect "com.saucelabs:ci-sauce:1.81"
}

sauceLabs {
    browsers { //<5>
       firefox_linux_19
       chrome_mac
       delegate."internet explorer_vista_9"
       nexus4 { //<6>
         capabilities(
            browserName: "android",
            platform: "Linux",
            version: "4.4",
            deviceName: "LG Nexus 4"
         )
       }
    }
    task { //<7>
       testClassesDir = test.testClassesDir
       testSrcDirs = test.testSrcDirs
       classpath = test.classpath
    }
    account { //<8>
       username = System.getenv(SauceAccount.USER_ENV_VAR)
       accessKey = System.getenv(SauceAccount.ACCESS_KEY_ENV_VAR)
    }
    connect { //<9>
       port = 4444 //<10>
       additionalOptions = ['--proxy', 'proxy.example.com:8080'] //<11>
    }
}
----
<1> Apply the plugin to the build.
<2> Specify how to resolve the plugin.
<3> Declare a repository for resolving SauceConnect.
<4> Declare version of SauceConnect to be used as part of the `sauceConnect` configuration. This will be used by tasks that open a {sauce-connect} tunnel before
running the generated test tasks which means that the browsers in the cloud will have localhost pointing at the machine running the build.
<5> Declare that tests should run in 3 different browsers using the shorthand syntax; this will generate the following `Test` tasks: `firefoxLinux19Test`, `chromeMacTest` and
`internet explorerVista9Test`.
<6> Explicitly specify the required browser capabilities if the shorthand syntax doesn't allow you to express all needed capabilities; the example will generate a `Test` task named `nexus4Test`.
<7> Configure all of the generated test tasks; for each of them the closure is run with delegate set to a test task being configured.
<8> Pass credentials for {sauce-connect}.
<9> Additionally configure {sauce-connect} if desired.
<10> Override the port used by SauceConnect, defaults to 4445.
<11> Pass additional link:https://docs.saucelabs.com/reference/sauce-connect/#command-line-options[command line options] to SauceConnect.

[TIP]
====
You can use `allSauceLabsTests` task that will depend on all of the generated test tasks to run all of them during a build.
====

=== geb-browserstack

Following is an example of using the geb-browserstack Gradle plugin.

[source,groovy,subs="attributes,verbatim"]
----
import geb.gradle.browserstack.BrowserStackAccount

apply plugin: "geb-browserstack" //<1>

buildscript { //<2>
    repositories {
       mavenCentral()
    }
    dependencies {
       classpath 'org.gebish:geb-gradle:{geb-version}'
    }
}

browserStack {
    application 'http://localhost:8080' //<3>
    forceLocal = true //<4>
    browsers { //<5>
       firefox_mac_19
       chrome_mac
       delegate."internet explorer_windows_9"
       nexus4 { //<6>
         capabilities browserName: "android", platform: "ANDROID", device: "Google Nexus 4"
       }
    }
    task { //<7>
       testClassesDir = test.testClassesDir
       testSrcDirs = test.testSrcDirs
       classpath = test.classpath
    }
    account { //<8>
       username = System.getenv(BrowserStackAccount.USER_ENV_VAR)
       accessKey = System.getenv(BrowserStackAccount.ACCESS_KEY_ENV_VAR)
    }
}
----
<1> Apply the plugin to the build.
<2> Specify how to resolve the plugin.
<3> Specify which urls the BrowserStack Tunnel should be able to access.
Multiple applications can be specified.
If no applications are specified, the tunnel will not be restricted to particular URLs.
<4> Configure BrowserStack tunnel to route all traffic via the local machine.
This configuration property controls the `-forcelocal` flag and the default value for it is `false`.
<5> Declare that tests should run in 3 different browsers using the shorthand syntax; this will generate the following `Test` tasks: `firefoxLinux19Test`, `chromeMacTest` and
`internet explorerVista9Test`.
<6> Explicitly specify the required browser capabilities if the shorthand syntax doesn't allow you to express all needed capabilities; the example will generate a `Test` task named `nexus4Test`.
<7> Configure all of the generated test tasks; for each of them the closure is run with delegate set to a test task being configured.
<8> Pass credentials for BrowserStack.

[TIP]
====
You can use `allBrowserStackTests` task that will depend on all of the generated test tasks to run all of them during a build.
====


[DOCS] Document CCS-supported APIs (#52708)

Explicitly notes the Elasticsearch API endpoints that support CCS.

This should deter users from attempting to use CCS with other API
endpoints, such as `GET <index>/_doc/<_id>`.

[chapter]
[[modules-cross-cluster-search]]
= Search across clusters

*{ccs-cap}* lets you run a single search request against one or more
<<modules-remote-clusters,remote clusters>>. For example, you can use a {ccs} to
filter and analyze log data stored on clusters in different data centers.

IMPORTANT: {ccs-cap} requires <<modules-remote-clusters, remote clusters>>.

[float]
[[ccs-supported-apis]]
== Supported APIs

The following APIs support {ccs}:

* <<search-search,Search>>
* <<search-multi-search,Multi search>>
* <<search-template,Search template>>
* <<multi-search-template,Multi search template>>

[float]
[[ccs-example]]
== {ccs-cap} examples

[float]
[[ccs-remote-cluster-setup]]
=== Remote cluster setup

To perform a {ccs}, you must have at least one remote cluster configured.

The following <<cluster-update-settings,cluster update settings>> API request
adds three remote clusters:`cluster_one`, `cluster_two`, and `cluster_three`.

[source,console]
--------------------------------
PUT _cluster/settings
{
  "persistent": {
    "cluster": {
      "remote": {
        "cluster_one": {
          "seeds": [
            "127.0.0.1:9300"
          ]
        },
        "cluster_two": {
          "seeds": [
            "127.0.0.1:9301"
          ]
        },
        "cluster_three": {
          "seeds": [
            "127.0.0.1:9302"
          ]
        }
      }
    }
  }
}
--------------------------------
// TEST[setup:host]
// TEST[s/127.0.0.1:930\d+/\${transport_host}/]

[float]
[[ccs-search-remote-cluster]]
=== Search a single remote cluster

The following <<search-search,search>> API request searches the
`twitter` index on a single remote cluster, `cluster_one`.

[source,console]
--------------------------------------------------
GET /cluster_one:twitter/_search
{
  "query": {
    "match": {
      "user": "kimchy"
    }
  }
}
--------------------------------------------------
// TEST[continued]
// TEST[setup:twitter]

The API returns the following response:

[source,console-result]
--------------------------------------------------
{
  "took": 150,
  "timed_out": false,
  "_shards": {
    "total": 1,
    "successful": 1,
    "failed": 0,
    "skipped": 0
  },
  "_clusters": {
    "total": 1,
    "successful": 1,
    "skipped": 0
  },
  "hits": {
    "total" : {
        "value": 1,
        "relation": "eq"
    },
    "max_score": 1,
    "hits": [
      {
        "_index": "cluster_one:twitter", <1>
        "_id": "0",
        "_score": 1,
        "_source": {
          "user": "kimchy",
          "date": "2009-11-15T14:12:12",
          "message": "trying out Elasticsearch",
          "likes": 0
        }
      }
    ]
  }
}
--------------------------------------------------
// TESTRESPONSE[s/"took": 150/"took": "$body.took"/]
// TESTRESPONSE[s/"max_score": 1/"max_score": "$body.hits.max_score"/]
// TESTRESPONSE[s/"_score": 1/"_score": "$body.hits.hits.0._score"/]

<1> The search response body includes the name of the remote cluster in the
`_index` parameter.

[float]
[[ccs-search-multi-remote-cluster]]
=== Search multiple remote clusters

The following <<search,search>> API request searches the `twitter` index on
three clusters:

* Your local cluster
* Two remote clusters, `cluster_one` and `cluster_two`

[source,console]
--------------------------------------------------
GET /twitter,cluster_one:twitter,cluster_two:twitter/_search
{
  "query": {
    "match": {
      "user": "kimchy"
    }
  }
}
--------------------------------------------------
// TEST[continued]

The API returns the following response:

[source,console-result]
--------------------------------------------------
{
  "took": 150,
  "timed_out": false,
  "num_reduce_phases": 4,
  "_shards": {
    "total": 3,
    "successful": 3,
    "failed": 0,
    "skipped": 0
  },
  "_clusters": {
    "total": 3,
    "successful": 3,
    "skipped": 0
  },
  "hits": {
    "total" : {
        "value": 3,
        "relation": "eq"
    },
    "max_score": 1,
    "hits": [
      {
        "_index": "twitter", <1>
        "_id": "0",
        "_score": 2,
        "_source": {
          "user": "kimchy",
          "date": "2009-11-15T14:12:12",
          "message": "trying out Elasticsearch",
          "likes": 0
        }
      },
      {
        "_index": "cluster_one:twitter", <2>
        "_id": "0",
        "_score": 1,
        "_source": {
          "user": "kimchy",
          "date": "2009-11-15T14:12:12",
          "message": "trying out Elasticsearch",
          "likes": 0
        }
      },
      {
        "_index": "cluster_two:twitter", <3>
        "_id": "0",
        "_score": 1,
        "_source": {
          "user": "kimchy",
          "date": "2009-11-15T14:12:12",
          "message": "trying out Elasticsearch",
          "likes": 0
        }
      }
    ]
  }
}
--------------------------------------------------
// TESTRESPONSE[s/"took": 150/"took": "$body.took"/]
// TESTRESPONSE[s/"max_score": 1/"max_score": "$body.hits.max_score"/]
// TESTRESPONSE[s/"_score": 1/"_score": "$body.hits.hits.0._score"/]
// TESTRESPONSE[s/"_score": 2/"_score": "$body.hits.hits.1._score"/]

<1> This document's `_index` parameter doesn't include a cluster name. This
means the document came from the local cluster.
<2> This document came from `cluster_one`.
<3> This document came from `cluster_two`.

[float]
[[skip-unavailable-clusters]]
== Skip unavailable clusters

By default, a {ccs} returns an error if *any* cluster in the request is
unavailable.

To skip an unavailable cluster during a {ccs}, set the
<<skip-unavailable,`skip_unavailable`>> cluster setting to `true`.

The following <<cluster-update-settings,cluster update settings>> API request
changes `cluster_two`'s `skip_unavailable` setting to `true`.

[source,console]
--------------------------------
PUT _cluster/settings
{
  "persistent": {
    "cluster.remote.cluster_two.skip_unavailable": true
  }
}
--------------------------------
// TEST[continued]

If `cluster_two` is disconnected or unavailable during a {ccs}, {es} won't
include matching documents from that cluster in the final results.

[discrete]
[[ccs-works]]
== How {ccs} works

include::./remote-clusters.asciidoc[tag=how-remote-clusters-work]

[discrete]
[[ccs-gateway-seed-nodes]]
=== Selecting gateway and seed nodes

Gateway and seed nodes need to be accessible from the local cluster via your
network.

By default, any master-ineligible node can act as a gateway node. If wanted,
you can define the gateway nodes for a cluster by setting
`cluster.remote.node.attr.gateway` to `true`.

For {ccs}, we recommend you use gateway nodes that are capable of serving as
<<coordinating-node,coordinating nodes>> for search requests. If
wanted, the seed nodes for a cluster can be a subset of these gateway nodes.

[discrete]
[[ccs-network-delays]]
=== How {ccs} handles network delays

Because {ccs} involves sending requests to remote clusters, any network delays
can impact search speed. To avoid slow searches, {ccs} offers two options for
handling network delays:

<<ccs-min-roundtrips,Minimize network roundtrips>>::
By default, {es} reduces the number of network roundtrips between remote
clusters. This reduces the impact of network delays on search speed. However,
{es} can't reduce network roundtrips for large search requests, such as those
including a <<request-body-search-scroll, scroll>> or
<<request-body-search-inner-hits,inner hits>>.
+
See <<ccs-min-roundtrips>> to learn how this option works.

<<ccs-unmin-roundtrips, Don't minimize network roundtrips>>:: For search
requests that include a scroll or inner hits, {es} sends multiple outgoing and
ingoing requests to each remote cluster. You can also choose this option by
setting the <<ccs-minimize-roundtrips,`ccs_minimize_roundtrips`>> parameter to
`false`. While typically slower, this approach may work well for networks with
low latency.
+
See <<ccs-unmin-roundtrips>> to learn how this option works.

[float]
[[ccs-min-roundtrips]]
==== Minimize network roundtrips

Here's how {ccs} works when you minimize network roundtrips.

. You send a {ccs} request to your local cluster. A coordinating node in that
cluster receives and parses the request.
+
image:images/ccs/ccs-min-roundtrip-client-request.svg[]

. The coordinating node sends a single search request to each cluster, including
the local cluster. Each cluster performs the search request independently,
applying its own cluster-level settings to the request.
+
image:images/ccs/ccs-min-roundtrip-cluster-search.svg[]

. Each remote cluster sends its search results back to the coordinating node.
+
image:images/ccs/ccs-min-roundtrip-cluster-results.svg[]

. After collecting results from each cluster, the coordinating node returns the
final results in the {ccs} response.
+
image:images/ccs/ccs-min-roundtrip-client-response.svg[]

[float]
[[ccs-unmin-roundtrips]]
==== Don't minimize network roundtrips

Here's how {ccs} works when you don't minimize network roundtrips.

. You send a {ccs} request to your local cluster. A coordinating node in that
cluster receives and parses the request.
+
image:images/ccs/ccs-min-roundtrip-client-request.svg[]

. The coordinating node sends a <<search-shards,search shards>> API request to
each remote cluster.
+
image:images/ccs/ccs-min-roundtrip-cluster-search.svg[]

. Each remote cluster sends its response back to the coordinating node.
This response contains information about the indices and shards the {ccs}
request will be executed on.
+
image:images/ccs/ccs-min-roundtrip-cluster-results.svg[]

. The coordinating node sends a search request to each shard, including those in
its own cluster. Each shard performs the search request independently.
+
[WARNING]
====
When network roundtrips aren't minimized, the search is executed as if all data
were in the coordinating node's cluster. We recommend updating cluster-level
settings that limit searches, such as `action.search.shard_count.limit`,
`pre_filter_shard_size`, and `max_concurrent_shard_requests`, to account for
this. If these limits are too low, the search may be rejected.
====
+
image:images/ccs/ccs-dont-min-roundtrip-shard-search.svg[]

. Each shard sends its search results back to the coordinating node.
+
image:images/ccs/ccs-dont-min-roundtrip-shard-results.svg[]

. After collecting results from each cluster, the coordinating node returns the
final results in the {ccs} response.
+
image:images/ccs/ccs-min-roundtrip-client-response.svg[]

[chapter]
[[modules-cross-cluster-search]]
= Search across clusters

*{ccs-cap}* lets you run a single search request against one or more
<<modules-remote-clusters,remote clusters>>. For example, you can use a {ccs} to
filter and analyze log data stored on clusters in different data centers.

IMPORTANT: {ccs-cap} requires <<modules-remote-clusters, remote clusters>>.

[float]
[[ccs-example]]
== {ccs-cap} examples

[float]
[[ccs-remote-cluster-setup]]
=== Remote cluster setup

To perform a {ccs}, you must have at least one remote cluster configured.

The following <<cluster-update-settings,cluster update settings>> API request
adds three remote clusters:`cluster_one`, `cluster_two`, and `cluster_three`.

[source,console]
--------------------------------
PUT _cluster/settings
{
  "persistent": {
    "cluster": {
      "remote": {
        "cluster_one": {
          "seeds": [
            "127.0.0.1:9300"
          ]
        },
        "cluster_two": {
          "seeds": [
            "127.0.0.1:9301"
          ]
        },
        "cluster_three": {
          "seeds": [
            "127.0.0.1:9302"
          ]
        }
      }
    }
  }
}
--------------------------------
// TEST[setup:host]
// TEST[s/127.0.0.1:930\d+/\${transport_host}/]

[float]
[[ccs-search-remote-cluster]]
=== Search a single remote cluster

The following <<search,search>> API request searches the
`twitter` index on a single remote cluster, `cluster_one`.

[source,console]
--------------------------------------------------
GET /cluster_one:twitter/_search
{
  "query": {
    "match": {
      "user": "kimchy"
    }
  }
}
--------------------------------------------------
// TEST[continued]
// TEST[setup:twitter]

The API returns the following response:

[source,console-result]
--------------------------------------------------
{
  "took": 150,
  "timed_out": false,
  "_shards": {
    "total": 1,
    "successful": 1,
    "failed": 0,
    "skipped": 0
  },
  "_clusters": {
    "total": 1,
    "successful": 1,
    "skipped": 0
  },
  "hits": {
    "total" : {
        "value": 1,
        "relation": "eq"
    },
    "max_score": 1,
    "hits": [
      {
        "_index": "cluster_one:twitter", <1>
        "_id": "0",
        "_score": 1,
        "_source": {
          "user": "kimchy",
          "date": "2009-11-15T14:12:12",
          "message": "trying out Elasticsearch",
          "likes": 0
        }
      }
    ]
  }
}
--------------------------------------------------
// TESTRESPONSE[s/"took": 150/"took": "$body.took"/]
// TESTRESPONSE[s/"max_score": 1/"max_score": "$body.hits.max_score"/]
// TESTRESPONSE[s/"_score": 1/"_score": "$body.hits.hits.0._score"/]

<1> The search response body includes the name of the remote cluster in the
`_index` parameter.

[float]
[[ccs-search-multi-remote-cluster]]
=== Search multiple remote clusters

The following <<search,search>> API request searches the `twitter` index on
three clusters:

* Your local cluster
* Two remote clusters, `cluster_one` and `cluster_two`

[source,console]
--------------------------------------------------
GET /twitter,cluster_one:twitter,cluster_two:twitter/_search
{
  "query": {
    "match": {
      "user": "kimchy"
    }
  }
}
--------------------------------------------------
// TEST[continued]

The API returns the following response:

[source,console-result]
--------------------------------------------------
{
  "took": 150,
  "timed_out": false,
  "num_reduce_phases": 4,
  "_shards": {
    "total": 3,
    "successful": 3,
    "failed": 0,
    "skipped": 0
  },
  "_clusters": {
    "total": 3,
    "successful": 3,
    "skipped": 0
  },
  "hits": {
    "total" : {
        "value": 3,
        "relation": "eq"
    },
    "max_score": 1,
    "hits": [
      {
        "_index": "twitter", <1>
        "_id": "0",
        "_score": 2,
        "_source": {
          "user": "kimchy",
          "date": "2009-11-15T14:12:12",
          "message": "trying out Elasticsearch",
          "likes": 0
        }
      },
      {
        "_index": "cluster_one:twitter", <2>
        "_id": "0",
        "_score": 1,
        "_source": {
          "user": "kimchy",
          "date": "2009-11-15T14:12:12",
          "message": "trying out Elasticsearch",
          "likes": 0
        }
      },
      {
        "_index": "cluster_two:twitter", <3>
        "_id": "0",
        "_score": 1,
        "_source": {
          "user": "kimchy",
          "date": "2009-11-15T14:12:12",
          "message": "trying out Elasticsearch",
          "likes": 0
        }
      }
    ]
  }
}
--------------------------------------------------
// TESTRESPONSE[s/"took": 150/"took": "$body.took"/]
// TESTRESPONSE[s/"max_score": 1/"max_score": "$body.hits.max_score"/]
// TESTRESPONSE[s/"_score": 1/"_score": "$body.hits.hits.0._score"/]
// TESTRESPONSE[s/"_score": 2/"_score": "$body.hits.hits.1._score"/]

<1> This document's `_index` parameter doesn't include a cluster name. This
means the document came from the local cluster.
<2> This document came from `cluster_one`.
<3> This document came from `cluster_two`.

[float]
[[skip-unavailable-clusters]]
== Skip unavailable clusters

By default, a {ccs} returns an error if *any* cluster in the request is
unavailable.

To skip an unavailable cluster during a {ccs}, set the
<<skip-unavailable,`skip_unavailable`>> cluster setting to `true`.

The following <<cluster-update-settings,cluster update settings>> API request
changes `cluster_two`'s `skip_unavailable` setting to `true`.

[source,console]
--------------------------------
PUT _cluster/settings
{
  "persistent": {
    "cluster.remote.cluster_two.skip_unavailable": true
  }
}
--------------------------------
// TEST[continued]

If `cluster_two` is disconnected or unavailable during a {ccs}, {es} won't
include matching documents from that cluster in the final results.

[discrete]
[[ccs-works]]
== How {ccs} works

include::./remote-clusters.asciidoc[tag=how-remote-clusters-work]

[discrete]
[[ccs-gateway-seed-nodes]]
=== Selecting gateway and seed nodes

Gateway and seed nodes need to be accessible from the local cluster via your
network.

By default, any master-ineligible node can act as a gateway node. If wanted,
you can define the gateway nodes for a cluster by setting
`cluster.remote.node.attr.gateway` to `true`.

For {ccs}, we recommend you use gateway nodes that are capable of serving as
<<coordinating-node,coordinating nodes>> for search requests. If
wanted, the seed nodes for a cluster can be a subset of these gateway nodes.

[discrete]
[[ccs-network-delays]]
=== How {ccs} handles network delays

Because {ccs} involves sending requests to remote clusters, any network delays
can impact search speed. To avoid slow searches, {ccs} offers two options for
handling network delays:

<<ccs-min-roundtrips,Minimize network roundtrips>>::
By default, {es} reduces the number of network roundtrips between remote
clusters. This reduces the impact of network delays on search speed. However,
{es} can't reduce network roundtrips for large search requests, such as those
including a <<request-body-search-scroll, scroll>> or
<<request-body-search-inner-hits,inner hits>>.
+
See <<ccs-min-roundtrips>> to learn how this option works.

<<ccs-unmin-roundtrips, Don't minimize network roundtrips>>::
For search requests that include a scroll or inner hits, {es} sends multiple
outgoing and ingoing requests to each remote cluster. You can also choose this
option by setting the <<search,search>> API's
<<ccs-minimize-roundtrips,`ccs_minimize_roundtrips`>> parameter to `false`.
While typically slower, this approach may work well for networks with low
latency.
+
See <<ccs-unmin-roundtrips>> to learn how this option works.

[float]
[[ccs-min-roundtrips]]
==== Minimize network roundtrips

Here's how {ccs} works when you minimize network roundtrips.

. You send a {ccs} request to your local cluster. A coordinating node in that
cluster receives and parses the request.
+
image:images/ccs/ccs-min-roundtrip-client-request.svg[]

. The coordinating node sends a single search request to each cluster, including
the local cluster. Each cluster performs the search request independently,
applying its own cluster-level settings to the request.
+
image:images/ccs/ccs-min-roundtrip-cluster-search.svg[]

. Each remote cluster sends its search results back to the coordinating node.
+
image:images/ccs/ccs-min-roundtrip-cluster-results.svg[]

. After collecting results from each cluster, the coordinating node returns the
final results in the {ccs} response.
+
image:images/ccs/ccs-min-roundtrip-client-response.svg[]

[float]
[[ccs-unmin-roundtrips]]
==== Don't minimize network roundtrips

Here's how {ccs} works when you don't minimize network roundtrips.

. You send a {ccs} request to your local cluster. A coordinating node in that
cluster receives and parses the request.
+
image:images/ccs/ccs-min-roundtrip-client-request.svg[]

. The coordinating node sends a <<search-shards,search shards>> API request to
each remote cluster.
+
image:images/ccs/ccs-min-roundtrip-cluster-search.svg[]

. Each remote cluster sends its response back to the coordinating node.
This response contains information about the indices and shards the {ccs}
request will be executed on.
+
image:images/ccs/ccs-min-roundtrip-cluster-results.svg[]

. The coordinating node sends a search request to each shard, including those in
its own cluster. Each shard performs the search request independently.
+
[WARNING]
====
When network roundtrips aren't minimized, the search is executed as if all data
were in the coordinating node's cluster. We recommend updating cluster-level
settings that limit searches, such as `action.search.shard_count.limit`,
`pre_filter_shard_size`, and `max_concurrent_shard_requests`, to account for
this. If these limits are too low, the search may be rejected.
====
+
image:images/ccs/ccs-dont-min-roundtrip-shard-search.svg[]

. Each shard sends its search results back to the coordinating node.
+
image:images/ccs/ccs-dont-min-roundtrip-shard-results.svg[]

. After collecting results from each cluster, the coordinating node returns the
final results in the {ccs} response.
+
image:images/ccs/ccs-min-roundtrip-client-response.svg[]