This question already has answers here:
What is a NullPointerException, and how do I fix it?
(12 answers)
Closed 6 years ago.
I'm working out of the book Learning Libgdx Game Development on Chapter 10 dealing with adding music and sound to the game. I tried Googling around to see if anyone else came across the same null pointer issue with adding the music but no luck.
Essentially trying to load in a Music track from a different class called Assets to run in my main game.
I see the assets are loaded, it prints out on the console. So I don't think it is an issue with not finding the file.
com.woodgdx.game.Assets: # of assets loaded: 9
com.woodgdx.game.Assets: asset: ../core/assets/sounds/jump_with_feather.wav
com.woodgdx.game.Assets: asset: ../core/assets/canyonbunny.pack.png
com.woodgdx.game.Assets: asset: ../core/assets/music/keith303_-_brand_new_highscore.mp3
com.woodgdx.game.Assets: asset: ../core/assets/sounds/live_lost.wav
com.woodgdx.game.Assets: asset: ../core/assets/canyonbunny.pack2.png
com.woodgdx.game.Assets: asset: ../core/assets/sounds/pickup_feather.wav
com.woodgdx.game.Assets: asset: ../core/assets/sounds/jump.wav
com.woodgdx.game.Assets: asset: ../core/assets/canyonbunny.pack.atlas
com.woodgdx.game.Assets: asset: ../core/assets/sounds/pickup_coin.wav
So I'm not really sure what is going on...
The error itself is..
Exception in thread "LWJGL Application" java.lang.NullPointerException
at com.woodgdx.game.woodGdxGame.create(woodGdxGame.java:29)
at com.badlogic.gdx.backends.lwjgl.LwjglApplication.mainLoop(LwjglApplication.java:147)
at com.badlogic.gdx.backends.lwjgl.LwjglApplication$1.run(LwjglApplication.java:124)
Main class
package com.woodgdx.game;
import com.badlogic.gdx.Application;
import com.badlogic.gdx.Game;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.assets.AssetManager;
import com.woodgdx.game.Assets;
import com.woodgdx.game.screens.MenuScreen;
import com.woodgdx.game.util.AudioManager;
import com.woodgdx.game.util.GamePreferences;
/**
* The main game file with the main
* engine methods
* #author xxxx
*/
public class woodGdxGame extends Game
{
#Override
public void create()
{
// Set Libgdx log level
Gdx.app.setLogLevel(Application.LOG_DEBUG);
// Load assets
Assets.instance.init(new AssetManager());
// Load preferences for audio settings and start playing music
GamePreferences.instance.load();
AudioManager.instance.play(Assets.instance.music.song01);
// Start game at menu screen
setScreen(new MenuScreen(this));
}
}
The error occurs on this line..
AudioManager.instance.play(Assets.instance.music.song01);
My Assets class contains this inner class. The way the book had it, I got the same error when I made it it's own class.
/**
* Music for game loaded
* #author xxxx
*
*/
public class AssetMusic
{
//Our music object
public final Music song01;
/**
* Retrieves the music file for the music
* #param am
*/
public AssetMusic(AssetManager am)
{
song01 = am.get("../core/assets/music/keith303_-_brand_new_highscore.mp3", Music.class);
}
}
Then here is the AudioManager class they does play a little role in here.
/**
* Manages the playing and organization
* of audio files
* #author xxxx
*
*/
public class AudioManager
{
public static final AudioManager instance = new AudioManager();
private Music playingMusic;
// singleton: prevent instantiation from other classes
private AudioManager()
{
}
/**
* Play sound with default vol, pitch, and pan (0)
* #param sound
*/
public void play(Sound sound)
{
play(sound, 1);
}
/**
* Play sound with default pitch and pan (0) but different vol
* #param sound
* #param volume
*/
public void play(Sound sound, float volume)
{
play(sound, volume, 1);
}
/**
* Play sound with default pan (0) with different pitch and vol
* #param sound
* #param volume
* #param pitch
*/
public void play(Sound sound, float volume, float pitch)
{
play(sound, volume, pitch, 0);
}
/**
* Plays sound with a vol, pitch, and pan activated
* #param sound
* #param volume
* #param pitch
* #param pan
*/
public void play(Sound sound, float volume, float pitch, float pan)
{
//If sound is muted, dont play
if (!GamePreferences.instance.sound)
return;
sound.play(GamePreferences.instance.volSound * volume, pitch, pan);
}
/**
* Plays music on loop
* #param music
*/
public void play(Music music)
{
stopMusic();
playingMusic = music;
if (GamePreferences.instance.music)
{
music.setLooping(true);
music.setVolume(GamePreferences.instance.volMusic);
music.play();
}
}
/**
* Ends the music loop of doom
*/
public void stopMusic()
{
if (playingMusic != null)
playingMusic.stop();
}
/**
* Checks settings for muted music, vol level
*/
public void onSettingsUpdated()
{
//No music available
if (playingMusic == null)
return;
//Sets volume
playingMusic.setVolume(GamePreferences.instance.volMusic);
//Checks if music is not muted
if (GamePreferences.instance.music)
{
//If music isn't playing, play it
if (!playingMusic.isPlaying())
playingMusic.play();
}
//Mute music if selected so
else
{
playingMusic.pause();
}
}
}
Forgot to add in the music and sound for the initialization for the Assets class. I was thinking it was something with the files but it just wasn't initialized.
Add this to the init() method of Assets
sounds = new AssetSounds(assetManager);
music = new AssetMusic(assetManager);
Related
So i found this turn-by-turn navigation example by mapbox.
It works just fine but but i would like to do some custom actions after the navigation has completed such as maybe enable a certain button that was initially hidden when the navigation was on progress or toast a message indicating that the navigation is completed. Since i'm actually new to Kotlin and the mapbox API in general, i can't really seem to figure out where in the code should i inject those particular actions.
Here is the Kotlin class that does the magic (I have discarded the import section):
/**
* This example demonstrates a basic turn-by-turn navigation experience by putting together some UI elements to showcase
* navigation camera transitions, guidance instructions banners and playback, and progress along the route.
*
* Before running the example make sure you have put your access_token in the correct place
* inside [app/src/main/res/values/mapbox_access_token.xml]. If not present then add this file
* at the location mentioned above and add the following content to it
*
* <?xml version="1.0" encoding="utf-8"?>
* <resources xmlns:tools="http://schemas.android.com/tools">
* <string name="mapbox_access_token"><PUT_YOUR_ACCESS_TOKEN_HERE></string>
* </resources>
*
* The example assumes that you have granted location permissions and does not enforce it. However,
* the permission is essential for proper functioning of this example. The example also uses replay
* location engine to facilitate navigation without actually physically moving.
*
* How to use this example:
* - You can long-click the map to select a destination.
* - The guidance will start to the selected destination while simulating location updates.
* You can disable simulation by commenting out the [replayLocationEngine] setter in [NavigationOptions].
* Then, the device's real location will be used.
* - At any point in time you can finish guidance or select a new destination.
* - You can use buttons to mute/unmute voice instructions, recenter the camera, or show the route overview.
*/
class TurnByTurnExperienceActivity : AppCompatActivity() {
private companion object {
private const val BUTTON_ANIMATION_DURATION = 1500L
}
/**
* Debug tool used to play, pause and seek route progress events that can be used to produce mocked location updates along the route.
*/
private val mapboxReplayer = MapboxReplayer()
/**
* Debug tool that mocks location updates with an input from the [mapboxReplayer].
*/
private val replayLocationEngine = ReplayLocationEngine(mapboxReplayer)
/**
* Debug observer that makes sure the replayer has always an up-to-date information to generate mock updates.
*/
private val replayProgressObserver = ReplayProgressObserver(mapboxReplayer)
/**
* Bindings to the example layout.
*/
private lateinit var binding: MapboxActivityTurnByTurnExperienceBinding
/**
* Mapbox Maps entry point obtained from the [MapView].
* You need to get a new reference to this object whenever the [MapView] is recreated.
*/
private lateinit var mapboxMap: MapboxMap
/**
* Mapbox Navigation entry point. There should only be one instance of this object for the app.
* You can use [MapboxNavigationProvider] to help create and obtain that instance.
*/
private lateinit var mapboxNavigation: MapboxNavigation
/**
* Used to execute camera transitions based on the data generated by the [viewportDataSource].
* This includes transitions from route overview to route following and continuously updating the camera as the location changes.
*/
private lateinit var navigationCamera: NavigationCamera
/**
* Produces the camera frames based on the location and routing data for the [navigationCamera] to execute.
*/
private lateinit var viewportDataSource: MapboxNavigationViewportDataSource
/*
* Below are generated camera padding values to ensure that the route fits well on screen while
* other elements are overlaid on top of the map (including instruction view, buttons, etc.)
*/
private val pixelDensity = Resources.getSystem().displayMetrics.density
private val overviewPadding: EdgeInsets by lazy {
EdgeInsets(
140.0 * pixelDensity,
40.0 * pixelDensity,
120.0 * pixelDensity,
40.0 * pixelDensity
)
}
private val landscapeOverviewPadding: EdgeInsets by lazy {
EdgeInsets(
30.0 * pixelDensity,
380.0 * pixelDensity,
110.0 * pixelDensity,
20.0 * pixelDensity
)
}
private val followingPadding: EdgeInsets by lazy {
EdgeInsets(
180.0 * pixelDensity,
40.0 * pixelDensity,
150.0 * pixelDensity,
40.0 * pixelDensity
)
}
private val landscapeFollowingPadding: EdgeInsets by lazy {
EdgeInsets(
30.0 * pixelDensity,
380.0 * pixelDensity,
110.0 * pixelDensity,
40.0 * pixelDensity
)
}
/**
* Generates updates for the [MapboxManeuverView] to display the upcoming maneuver instructions
* and remaining distance to the maneuver point.
*/
private lateinit var maneuverApi: MapboxManeuverApi
/**
* Generates updates for the [MapboxTripProgressView] that include remaining time and distance to the destination.
*/
private lateinit var tripProgressApi: MapboxTripProgressApi
/**
* Generates updates for the [routeLineView] with the geometries and properties of the routes that should be drawn on the map.
*/
private lateinit var routeLineApi: MapboxRouteLineApi
/**
* Draws route lines on the map based on the data from the [routeLineApi]
*/
private lateinit var routeLineView: MapboxRouteLineView
/**
* Generates updates for the [routeArrowView] with the geometries and properties of maneuver arrows that should be drawn on the map.
*/
private val routeArrowApi: MapboxRouteArrowApi = MapboxRouteArrowApi()
/**
* Draws maneuver arrows on the map based on the data [routeArrowApi].
*/
private lateinit var routeArrowView: MapboxRouteArrowView
/**
* Stores and updates the state of whether the voice instructions should be played as they come or muted.
*/
private var isVoiceInstructionsMuted = false
set(value) {
field = value
if (value) {
binding.soundButton.muteAndExtend(BUTTON_ANIMATION_DURATION)
voiceInstructionsPlayer.volume(SpeechVolume(0f))
} else {
binding.soundButton.unmuteAndExtend(BUTTON_ANIMATION_DURATION)
voiceInstructionsPlayer.volume(SpeechVolume(1f))
}
}
/**
* Extracts message that should be communicated to the driver about the upcoming maneuver.
* When possible, downloads a synthesized audio file that can be played back to the driver.
*/
private lateinit var speechApi: MapboxSpeechApi
/**
* Plays the synthesized audio files with upcoming maneuver instructions
* or uses an on-device Text-To-Speech engine to communicate the message to the driver.
*/
private lateinit var voiceInstructionsPlayer: MapboxVoiceInstructionsPlayer
/**
* Observes when a new voice instruction should be played.
*/
private val voiceInstructionsObserver = VoiceInstructionsObserver { voiceInstructions ->
speechApi.generate(voiceInstructions, speechCallback)
}
/**
* Based on whether the synthesized audio file is available, the callback plays the file
* or uses the fall back which is played back using the on-device Text-To-Speech engine.
*/
private val speechCallback =
MapboxNavigationConsumer<Expected<SpeechError, SpeechValue>> { expected ->
expected.fold(
{ error ->
// play the instruction via fallback text-to-speech engine
voiceInstructionsPlayer.play(
error.fallback,
voiceInstructionsPlayerCallback
)
},
{ value ->
// play the sound file from the external generator
voiceInstructionsPlayer.play(
value.announcement,
voiceInstructionsPlayerCallback
)
}
)
}
/**
* When a synthesized audio file was downloaded, this callback cleans up the disk after it was played.
*/
private val voiceInstructionsPlayerCallback =
MapboxNavigationConsumer<SpeechAnnouncement> { value ->
// remove already consumed file to free-up space
speechApi.clean(value)
}
/**
* [NavigationLocationProvider] is a utility class that helps to provide location updates generated by the Navigation SDK
* to the Maps SDK in order to update the user location indicator on the map.
*/
private val navigationLocationProvider = NavigationLocationProvider()
/**
* Gets notified with location updates.
*
* Exposes raw updates coming directly from the location services
* and the updates enhanced by the Navigation SDK (cleaned up and matched to the road).
*/
private val locationObserver = object : LocationObserver {
var firstLocationUpdateReceived = false
override fun onNewRawLocation(rawLocation: Location) {
// not handled
}
override fun onNewLocationMatcherResult(locationMatcherResult: LocationMatcherResult) {
val enhancedLocation = locationMatcherResult.enhancedLocation
// update location puck's position on the map
navigationLocationProvider.changePosition(
location = enhancedLocation,
keyPoints = locationMatcherResult.keyPoints,
)
// update camera position to account for new location
viewportDataSource.onLocationChanged(enhancedLocation)
viewportDataSource.evaluate()
// if this is the first location update the activity has received,
// it's best to immediately move the camera to the current user location
if (!firstLocationUpdateReceived) {
firstLocationUpdateReceived = true
navigationCamera.requestNavigationCameraToOverview(
stateTransitionOptions = NavigationCameraTransitionOptions.Builder()
.maxDuration(0) // instant transition
.build()
)
}
}
}
/**
* Gets notified with progress along the currently active route.
*/
private val routeProgressObserver = RouteProgressObserver { routeProgress ->
// update the camera position to account for the progressed fragment of the route
viewportDataSource.onRouteProgressChanged(routeProgress)
viewportDataSource.evaluate()
// draw the upcoming maneuver arrow on the map
val style = mapboxMap.getStyle()
if (style != null) {
val maneuverArrowResult = routeArrowApi.addUpcomingManeuverArrow(routeProgress)
routeArrowView.renderManeuverUpdate(style, maneuverArrowResult)
}
// update top banner with maneuver instructions
val maneuvers = maneuverApi.getManeuvers(routeProgress)
maneuvers.fold(
{ error ->
Toast.makeText(
this#TurnByTurnExperienceActivity,
error.errorMessage,
Toast.LENGTH_SHORT
).show()
},
{
binding.maneuverView.visibility = View.VISIBLE
binding.maneuverView.renderManeuvers(maneuvers)
}
)
// update bottom trip progress summary
binding.tripProgressView.render(
tripProgressApi.getTripProgress(routeProgress)
)
}
/**
* Gets notified whenever the tracked routes change.
*
* A change can mean:
* - routes get changed with [MapboxNavigation.setRoutes]
* - routes annotations get refreshed (for example, congestion annotation that indicate the live traffic along the route)
* - driver got off route and a reroute was executed
*/
private val routesObserver = RoutesObserver { routeUpdateResult ->
if (routeUpdateResult.routes.isNotEmpty()) {
// generate route geometries asynchronously and render them
val routeLines = routeUpdateResult.routes.map { RouteLine(it, null) }
routeLineApi.setRoutes(
routeLines
) { value ->
mapboxMap.getStyle()?.apply {
routeLineView.renderRouteDrawData(this, value)
}
}
// update the camera position to account for the new route
viewportDataSource.onRouteChanged(routeUpdateResult.routes.first())
viewportDataSource.evaluate()
} else {
// remove the route line and route arrow from the map
val style = mapboxMap.getStyle()
if (style != null) {
routeLineApi.clearRouteLine { value ->
routeLineView.renderClearRouteLineValue(
style,
value
)
}
routeArrowView.render(style, routeArrowApi.clearArrows())
}
// remove the route reference from camera position evaluations
viewportDataSource.clearRouteData()
viewportDataSource.evaluate()
}
}
#SuppressLint("MissingPermission")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = MapboxActivityTurnByTurnExperienceBinding.inflate(layoutInflater)
setContentView(binding.root)
mapboxMap = binding.mapView.getMapboxMap()
// initialize the location puck
binding.mapView.location.apply {
this.locationPuck = LocationPuck2D(
bearingImage = ContextCompat.getDrawable(
this#TurnByTurnExperienceActivity,
R.drawable.mapbox_navigation_puck_icon
)
)
setLocationProvider(navigationLocationProvider)
enabled = true
}
// initialize Mapbox Navigation
mapboxNavigation = if (MapboxNavigationProvider.isCreated()) {
MapboxNavigationProvider.retrieve()
} else {
MapboxNavigationProvider.create(
NavigationOptions.Builder(this.applicationContext)
.accessToken(getString(R.string.mapbox_access_token))
// comment out the location engine setting block to disable simulation
.locationEngine(replayLocationEngine)
.build()
)
}
// initialize Navigation Camera
viewportDataSource = MapboxNavigationViewportDataSource(mapboxMap)
navigationCamera = NavigationCamera(
mapboxMap,
binding.mapView.camera,
viewportDataSource
)
// set the animations lifecycle listener to ensure the NavigationCamera stops
// automatically following the user location when the map is interacted with
binding.mapView.camera.addCameraAnimationsLifecycleListener(
NavigationBasicGesturesHandler(navigationCamera)
)
navigationCamera.registerNavigationCameraStateChangeObserver { navigationCameraState ->
// shows/hide the recenter button depending on the camera state
when (navigationCameraState) {
NavigationCameraState.TRANSITION_TO_FOLLOWING,
NavigationCameraState.FOLLOWING -> binding.recenter.visibility = View.INVISIBLE
NavigationCameraState.TRANSITION_TO_OVERVIEW,
NavigationCameraState.OVERVIEW,
NavigationCameraState.IDLE -> binding.recenter.visibility = View.VISIBLE
}
}
// set the padding values depending on screen orientation and visible view layout
if (this.resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) {
viewportDataSource.overviewPadding = landscapeOverviewPadding
} else {
viewportDataSource.overviewPadding = overviewPadding
}
if (this.resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) {
viewportDataSource.followingPadding = landscapeFollowingPadding
} else {
viewportDataSource.followingPadding = followingPadding
}
// make sure to use the same DistanceFormatterOptions across different features
val distanceFormatterOptions = mapboxNavigation.navigationOptions.distanceFormatterOptions
// initialize maneuver api that feeds the data to the top banner maneuver view
maneuverApi = MapboxManeuverApi(
MapboxDistanceFormatter(distanceFormatterOptions)
)
// initialize bottom progress view
tripProgressApi = MapboxTripProgressApi(
TripProgressUpdateFormatter.Builder(this)
.distanceRemainingFormatter(
DistanceRemainingFormatter(distanceFormatterOptions)
)
.timeRemainingFormatter(
TimeRemainingFormatter(this)
)
.percentRouteTraveledFormatter(
PercentDistanceTraveledFormatter()
)
.estimatedTimeToArrivalFormatter(
EstimatedTimeToArrivalFormatter(this, TimeFormat.NONE_SPECIFIED)
)
.build()
)
// initialize voice instructions api and the voice instruction player
speechApi = MapboxSpeechApi(
this,
getString(R.string.mapbox_access_token),
Locale.US.language
)
voiceInstructionsPlayer = MapboxVoiceInstructionsPlayer(
this,
getString(R.string.mapbox_access_token),
Locale.US.language
)
// initialize route line, the withRouteLineBelowLayerId is specified to place
// the route line below road labels layer on the map
// the value of this option will depend on the style that you are using
// and under which layer the route line should be placed on the map layers stack
val mapboxRouteLineOptions = MapboxRouteLineOptions.Builder(this)
.withRouteLineBelowLayerId("road-label")
.build()
routeLineApi = MapboxRouteLineApi(mapboxRouteLineOptions)
routeLineView = MapboxRouteLineView(mapboxRouteLineOptions)
// initialize maneuver arrow view to draw arrows on the map
val routeArrowOptions = RouteArrowOptions.Builder(this).build()
routeArrowView = MapboxRouteArrowView(routeArrowOptions)
// load map style
mapboxMap.loadStyleUri(
Style.MAPBOX_STREETS
) {
// add long click listener that search for a route to the clicked destination
binding.mapView.gestures.addOnMapLongClickListener { point ->
findRoute(point)
true
}
}
// initialize view interactions
binding.stop.setOnClickListener {
clearRouteAndStopNavigation()
}
binding.recenter.setOnClickListener {
navigationCamera.requestNavigationCameraToFollowing()
binding.routeOverview.showTextAndExtend(BUTTON_ANIMATION_DURATION)
}
binding.routeOverview.setOnClickListener {
navigationCamera.requestNavigationCameraToOverview()
binding.recenter.showTextAndExtend(BUTTON_ANIMATION_DURATION)
}
binding.soundButton.setOnClickListener {
// mute/unmute voice instructions
isVoiceInstructionsMuted = !isVoiceInstructionsMuted
}
// set initial sounds button state
binding.soundButton.unmute()
// start the trip session to being receiving location updates in free drive
// and later when a route is set also receiving route progress updates
mapboxNavigation.startTripSession()
}
override fun onStart() {
super.onStart()
// register event listeners
mapboxNavigation.registerRoutesObserver(routesObserver)
mapboxNavigation.registerRouteProgressObserver(routeProgressObserver)
mapboxNavigation.registerLocationObserver(locationObserver)
mapboxNavigation.registerVoiceInstructionsObserver(voiceInstructionsObserver)
mapboxNavigation.registerRouteProgressObserver(replayProgressObserver)
if (mapboxNavigation.getRoutes().isEmpty()) {
// if simulation is enabled (ReplayLocationEngine set to NavigationOptions)
// but we're not simulating yet,
// push a single location sample to establish origin
mapboxReplayer.pushEvents(
listOf(
ReplayRouteMapper.mapToUpdateLocation(
eventTimestamp = 0.0,
point = Point.fromLngLat(-122.39726512303575, 37.785128345296805)
)
)
)
mapboxReplayer.playFirstLocation()
}
}
override fun onStop() {
super.onStop()
// unregister event listeners to prevent leaks or unnecessary resource consumption
mapboxNavigation.unregisterRoutesObserver(routesObserver)
mapboxNavigation.unregisterRouteProgressObserver(routeProgressObserver)
mapboxNavigation.unregisterLocationObserver(locationObserver)
mapboxNavigation.unregisterVoiceInstructionsObserver(voiceInstructionsObserver)
mapboxNavigation.unregisterRouteProgressObserver(replayProgressObserver)
}
override fun onDestroy() {
super.onDestroy()
MapboxNavigationProvider.destroy()
speechApi.cancel()
voiceInstructionsPlayer.shutdown()
}
private fun findRoute(destination: Point) {
val originLocation = navigationLocationProvider.lastLocation
val originPoint = originLocation?.let {
Point.fromLngLat(it.longitude, it.latitude)
} ?: return
// execute a route request
// it's recommended to use the
// applyDefaultNavigationOptions and applyLanguageAndVoiceUnitOptions
// that make sure the route request is optimized
// to allow for support of all of the Navigation SDK features
mapboxNavigation.requestRoutes(
RouteOptions.builder()
.applyDefaultNavigationOptions()
.applyLanguageAndVoiceUnitOptions(this)
.coordinatesList(listOf(originPoint, destination))
// provide the bearing for the origin of the request to ensure
// that the returned route faces in the direction of the current user movement
.bearingsList(
listOf(
Bearing.builder()
.angle(originLocation.bearing.toDouble())
.degrees(45.0)
.build(),
null
)
)
.build(),
object : RouterCallback {
override fun onRoutesReady(
routes: List<DirectionsRoute>,
routerOrigin: RouterOrigin
) {
setRouteAndStartNavigation(routes)
}
override fun onFailure(
reasons: List<RouterFailure>,
routeOptions: RouteOptions
) {
// no impl
}
override fun onCanceled(routeOptions: RouteOptions, routerOrigin: RouterOrigin) {
// no impl
}
}
)
}
private fun setRouteAndStartNavigation(routes: List<DirectionsRoute>) {
// set routes, where the first route in the list is the primary route that
// will be used for active guidance
mapboxNavigation.setRoutes(routes)
// start location simulation along the primary route
startSimulation(routes.first())
// show UI elements
binding.soundButton.visibility = View.VISIBLE
binding.routeOverview.visibility = View.VISIBLE
binding.tripProgressCard.visibility = View.VISIBLE
// move the camera to overview when new route is available
navigationCamera.requestNavigationCameraToOverview()
}
private fun clearRouteAndStopNavigation() {
// clear
mapboxNavigation.setRoutes(listOf())
// stop simulation
mapboxReplayer.stop()
// hide UI elements
binding.soundButton.visibility = View.INVISIBLE
binding.maneuverView.visibility = View.INVISIBLE
binding.routeOverview.visibility = View.INVISIBLE
binding.tripProgressCard.visibility = View.INVISIBLE
}
private fun startSimulation(route: DirectionsRoute) {
mapboxReplayer.run {
stop()
clearEvents()
val replayEvents = ReplayRouteMapper().mapDirectionsRouteGeometry(route)
pushEvents(replayEvents)
seekTo(replayEvents.first())
play()
}
}
}
So can someone help me where or how to inject something like
if(navigation == completed){
//Do some stuff
}
You already have a RouteProgressObserver, so you could check the route state and when it is COMPLETE you can do something. See https://docs.mapbox.com/android/navigation/guides/turn-by-turn-navigation/route-progress/#listen-to-progress-change for more information about route progress.
Description
I want to build notification app which app provide notification every 15 minutes.
Build this app i use FirebaseJobDispatcher job scheduler.But when i build my job using newJobBuilder(). but this time when i use setService() method then i got compile time error.I can not fix it. I have searched for an appropriate solution over the web but didn't find anything useful. Please help me.
My Exception
setService
(java.lang.Class)
in Builder cannot be applied
to
(java.lang.Class)
Photo
My WaterReminderFirebaseJobService Class
package com.example.android.background.sync;
import android.app.job.JobParameters;
import android.app.job.JobService;
import android.content.Context;
import android.os.AsyncTask;
import com.firebase.jobdispatcher.RetryStrategy;
public class WaterReminderFirebaseJobService extends JobService {
private AsyncTask mBackgroundTask;
/**
* The entry point to your Job. Implementations should offload work to another thread of
* execution as soon as possible.
*
* This is called by the Job Dispatcher to tell us we should start our job. Keep in mind this
* method is run on the application's main thread, so we need to offload work to a background
* thread.
*
* #return whether there is more work remaining.
*/
#Override
public boolean onStartJob(final JobParameters jobParameters) {
// AsyncTask called mBackgroundTask.
// Here's where we make an AsyncTask so that this is no longer on the main thread
mBackgroundTask = new AsyncTask() {
// COMPLETED (6) Override doInBackground
#Override
protected Object doInBackground(Object[] params) {
// COMPLETED (7) Use ReminderTasks to execute the new charging reminder task you made, use
// this service as the context (WaterReminderFirebaseJobService.this) and return null
// when finished.
Context context = WaterReminderFirebaseJobService.this;
ReminderTasks.executeTask(context, ReminderTasks.ACTION_CHARGING_REMINDER);
return null;
}
#Override
protected void onPostExecute(Object o) {
// COMPLETED (8) Override onPostExecute and called jobFinished. Pass the job parameters
// and false to jobFinished. This will inform the JobManager that your job is done
// and that you do not want to reschedule the job.
/*
* Once the AsyncTask is finished, the job is finished. To inform JobManager that
* you're done, you call jobFinished with the jobParamters that were passed to your
* job and a boolean representing whether the job needs to be rescheduled. This is
* usually if something didn't work and you want the job to try running again.
*/
jobFinished(jobParameters, false);
}
};
mBackgroundTask.execute();
return true;
}
/**
* Called when the scheduling engine has decided to interrupt the execution of a running job,
* most likely because the runtime constraints associated with the job are no longer satisfied.
*
* #return whether the job should be retried
* #see RetryStrategy
*/
#Override
public boolean onStopJob(JobParameters jobParameters) {
if (mBackgroundTask != null) mBackgroundTask.cancel(true);
return true;
}
}
My ReminderUtils Class
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.background.sync;
import android.content.Context;
import android.support.annotation.NonNull;
import com.firebase.jobdispatcher.Constraint;
import com.firebase.jobdispatcher.Driver;
import com.firebase.jobdispatcher.FirebaseJobDispatcher;
import com.firebase.jobdispatcher.GooglePlayDriver;
import com.firebase.jobdispatcher.Job;
import com.firebase.jobdispatcher.Lifetime;
import com.firebase.jobdispatcher.Trigger;
import java.util.concurrent.TimeUnit;
public class ReminderUtilities {
private static final int REMINDER_INTERVAL_MINUTES=1;
private static final int REMINDER_INTERVAL_SECONDS=(int)(TimeUnit.MINUTES.toSeconds(REMINDER_INTERVAL_MINUTES));
private static final int SYNC_FLEXTIME_SECONDS=REMINDER_INTERVAL_SECONDS;
private static final String REMINDER_JOB_TAG="hydration_reminder_tag";
private static boolean sInitialized;
synchronized public static void scheduleChargingReminder(#NonNull final Context context){
if(sInitialized) return;
Driver driver=new GooglePlayDriver(context);
FirebaseJobDispatcher dispatcher=new FirebaseJobDispatcher(driver);
/* Create the Job to periodically create reminders to drink water */
Job constraintReminderJob = dispatcher.newJobBuilder()
/* The Service that will be used to write to preferences */
.setService(WaterReminderFirebaseJobService.class)
/*
* Set the UNIQUE tag used to identify this Job.
*/
.setTag(REMINDER_JOB_TAG)
/*
* Network constraints on which this Job should run. In this app, we're using the
* device charging constraint so that the job only executes if the device is
* charging.
*
* In a normal app, it might be a good idea to include a preference for this,
* as different users may have different preferences on when you should be
* syncing your application's data.
*/
.setConstraints(Constraint.DEVICE_CHARGING)
/*
* setLifetime sets how long this job should persist. The options are to keep the
* Job "forever" or to have it die the next time the device boots up.
*/
.setLifetime(Lifetime.FOREVER)
/*
* We want these reminders to continuously happen, so we tell this Job to recur.
*/
.setRecurring(true)
/*
* We want the reminders to happen every 15 minutes or so. The first argument for
* Trigger class's static executionWindow method is the start of the time frame
* when the
* job should be performed. The second argument is the latest point in time at
* which the data should be synced. Please note that this end time is not
* guaranteed, but is more of a guideline for FirebaseJobDispatcher to go off of.
*/
.setTrigger(Trigger.executionWindow(
REMINDER_INTERVAL_SECONDS,
REMINDER_INTERVAL_SECONDS + SYNC_FLEXTIME_SECONDS))
/*
* If a Job with the tag with provided already exists, this new job will replace
* the old one.
*/
.setReplaceCurrent(true)
/* Once the Job is ready, call the builder's build method to return the Job */
.build();
/* Schedule the Job with the dispatcher */
dispatcher.schedule(constraintReminderJob);
/* The job has been initialized */
sInitialized = true;
}
}
I am using JLayer to stream online radio music so a simple code for this is here:(Where StreamPlayer is a special implementation of JLayer)
//Radio Station URL example is http://radio.flex.ru:8000/radionami
/**
* Plays the Given Online Stream
*
* #param url
* <b> The Specified url you want to connect </b>
* #throws IOException
* #throws JavaLayerException
*/
public void playRadioStream(URL spec) {
try {
// Connection
URLConnection urlConnection = spec.openConnection();
// Connecting
urlConnection.connect();
// Try to play it
StreamPlayer player = new StreamPlayer();
player.open((urlConnection.getInputStream()));
player.play();
} catch (StreamPlayerException | IOException e) {
e.printStackTrace();
}
}
The problem:
I can't figure out how to retrieve information from this connection like the song is playing now from this Radio Station or the name of Station etc...The help is really appreciated!!
Edit:
If you want you can use JLayer instead of StreamPlayer it will work ,although you have to run it on different Thread from the main app Thread.
Would someone be able to help me please.
I am trying to complete exercise 13 from chapter 8 of the Art and Science of Java and have became stuck.
The program displays a set of towers (GRects), and then the user needs to click on any tower to have it send a signal to 'light (change the color)' the next tower in the chain. In the background with system.out I can see the signals being sent, however the event dispatcher waits (I read on another site) for this process to finish before updating the colors. Each object also has a time thread.sleep(300). My question is: Can someone please teach me how to write a new thread or show me what I am doing wrong and how I should go about fixing it. I have looked at Oracle docs on threads and can't understand what everything is, I am also new to Java and and doing self-study. While getting the exercise working is needed, I really want to learn how to make a program like this so that in the future I don't get stuck again. Any help from the online community would be greatly appreciated. Thank you in advance.
The code:
/**
* Class name: SignalTower
* -----------------------
* This class defines a signal tower object that passes a message to the
* next tower in a line.
*
* Programmer:
* Date: 2015/12/25
*/
package com.chapter8;
import java.awt.Color;
import acm.graphics.GCompound;
import acm.graphics.GLabel;
import acm.graphics.GRect;
/* Class: SignalTower */
/**
* This class defines a signal tower object that passes a message
* to the next tower in a line.
*/
public class SignalTower extends GCompound {
GRect tower = new GRect(TOWER_WIDTH,TOWER_HEIGHT);
/* Constructor: SignalTower(name, link) */
/**
* Constructs a new signal tower with the following parameters:
*
* #param name The name of the tower
* #param link A link to the next tower, or null if none exists
*/
public SignalTower(String name, SignalTower link) {
towerName = name;
nextTower = link;
buildTower();
}
/* Method: signal() */
/**
* This method represents sending a signal to this tower. The effect
* is to light the signal fire here and to send an additional signal
* message to the next tower in the chain, if any.
*/
public void signal() {
lightCurrentTower();
if (nextTower != null) nextTower.signal();
}
/* Method: lightCurrentTower() */
/**
* This method lights the signal fire for this tower. This version
* supplies a temporary implementation (typically called a "stub")
* that simply prints the name of the tower to the standard output
* channel. If you wanted to redesign this class to be part of a
* graphical application, for example, you could override this
* method to draw an indication of the signal fire on the display.
*/
public void lightCurrentTower() {
try {
this.setTowerColor();
Thread.sleep(300);
} catch (InterruptedException e) {
// Handle here
}
System.out.println("Lighting " + towerName);
}
public void buildTower(){
tower.setFilled(false);
add(tower);
GLabel label = new GLabel(towerName);
label.setLocation(getTowerWidth()/2 - label.getWidth()/2, this.getTowerLabelY());
add(label);
}
public void setTowerColor(){
tower.setColor(Color.RED);
tower.setFillColor(Color.RED);
tower.setFilled(true);
}
public int getTowerWidth(){
return TOWER_WIDTH;
}
public int getTowerSpace(){
return TOWER_SPACE;
}
public int getTowerLabelY(){
return TOWER_LABEL_Y;
}
/* Private instance variables */
private String towerName; /* The name of this tower */
private SignalTower nextTower; /* A link to the next tower */
private static final int TOWER_WIDTH = 30;
private static final int TOWER_HEIGHT = 180;
private static final int TOWER_SPACE = 100;
private static final int TOWER_LABEL_Y = 200;
}
/**
* File name: BeaconsOfGondor.java
* -------------------------------
* Message passing in linked structures: The beacons of Gondor:
*
* For answer Gandalf cried aloud to his horse. “On, Shadowfax! We must hasten.
* Time is short. See! The beacons of Gondor are alight, calling for aid. War
* is kindled. See, there is the fire on Amon Dîn, and flame on Eilenach; and
* there they go speeding west: Nardol, Erelas, Min-Rimmon, Calenhad, and the
* Halifirien on the borders of Rohan.”
* —J. R. R. Tolkien, The Return of the King, 1955
*
* This program creates a graphical representation of linked structures using
* this example.
*
* Programmer: Peter Lock
* Date: 2016/1/5
*/
package com.chapter8;
import java.awt.Point;
import java.awt.event.MouseEvent;
import acm.program.GraphicsProgram;
public class BeaconsOfGondor extends GraphicsProgram implements Runnable{
private Object gobj;
public String flag = "";
SignalTower[] towers = new SignalTower[10];
SignalTower rohan = new SignalTower("Rohan", null);
SignalTower halifirien = new SignalTower("Halifirien", rohan);
SignalTower calenhad = new SignalTower("Calenhad", halifirien);
SignalTower minRimmon = new SignalTower("Min-Rimmon", calenhad);
SignalTower erelas = new SignalTower("Erelas", minRimmon);
SignalTower nardol = new SignalTower("Nardol", erelas);
SignalTower eilenach = new SignalTower("Eilenach", nardol);
SignalTower amonDin = new SignalTower("Amon Din", eilenach);
SignalTower minasTirith = new SignalTower("Minas Tirith", amonDin);
public void run(){
createTowers();
addMouseListeners();
// minasTirith.signal();
}
private void createTowers() {
add(minasTirith);
minasTirith.setLocation(minasTirith.getTowerSpace(), 30);
add(amonDin);
amonDin.setLocation(amonDin.getTowerSpace()*2, 30);
add(eilenach);
eilenach.setLocation(eilenach.getTowerSpace()*3, 30);
add(nardol);
nardol.setLocation(nardol.getTowerSpace()*4, 30);
add(erelas);
erelas.setLocation(erelas.getTowerSpace()*5, 30);
add(minRimmon);
minRimmon.setLocation(minRimmon.getTowerSpace()*6, 30);
add(calenhad);
calenhad.setLocation(calenhad.getTowerSpace()*7, 30);
add(halifirien);
halifirien.setLocation(halifirien.getTowerSpace()*8, 30);
add(rohan);
rohan.setLocation(rohan.getTowerSpace()*9, 30);
}
public void mousePressed(MouseEvent e) {
gobj = getElementAt(e.getX(), e.getY());
if(gobj != null){
Point mp = e.getPoint();
if(minasTirith.contains(mp.getX(), mp.getY())) minasTirith.signal();
if(amonDin.contains(mp.getX(), mp.getY())) amonDin.signal();
if(eilenach.contains(mp.getX(), mp.getY())) eilenach.signal();
if(nardol.contains(mp.getX(), mp.getY())) nardol.signal();
if(erelas.contains(mp.getX(), mp.getY())) erelas.signal();
if(minRimmon.contains(mp.getX(), mp.getY())) minRimmon.signal();
if(calenhad.contains(mp.getX(), mp.getY())) calenhad.signal();
if(halifirien.contains(mp.getX(), mp.getY())) halifirien.signal();
if(rohan.contains(mp.getX(), mp.getY())) rohan.signal();
}
}
}
I've recently run into a strange bug with playing midis in a game I'm developing. I thought my midi code was working fine since it used to play midis without sounding weird. Now whenever it plays midis, they sound all tinny, echo-y, and loud.
I haven't touched my midi player code for a very long time, so I'm wondering if it's possible that a recent Java update has exposed a bug that has been in my code all along. Or perhaps there's some sort of midi bug in my version of Java that I'm not aware of?
The midis sound fine whenever I play them outside of my game.
I'm running Java 6, update 31, build 1.6.0_31-b05. Here's a SSCCE that reproduces the problem (it reproduces it on my JVM at least):
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.io.*;
import javax.sound.midi.*;
import java.net.URL;
public class MidiSSCCE extends JFrame
{
public MidiSSCCE()
{
super("Sound problem SSCCE");
this.setSize(200,100);
// instantiate main window panel
JPanel screenP = new SSCCEPanel(this);
this.add(screenP);
// finishing touches on Game window
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setVisible(true);
System.out.println("Game Window successfully created!!!");
}
public static void main(String[] args)
{
MidiSSCCE gui = new MidiSSCCE();
}
}
/**
* SSCCEPanel is the JPanel that manages the example's timer, painting, and logic.
**/
class SSCCEPanel extends JPanel
{
public Frame parentFrame;
private Timer timer;
public int logicLoops;
public double prevFPS;
boolean timerReady;
// The MidiPlayer object is used by the example to play the midi.
public MidiPlayer midiPlayer;
public SSCCEPanel(Frame parent)
{
super(true);
parentFrame = parent;
this.setFocusable(true);
Toolkit.getDefaultToolkit().sync();
logicLoops = 0;
midiPlayer = new MidiPlayer();
TimerListener timerListener = new TimerListener();
prevFPS = 0;
timerReady = true;
timer = new Timer(0,timerListener);
this.setFPS(60);
timer.start();
}
/**
* setFPS()
* Preconditions: fps is a quantity of frames per second
* Postconditions: Sets the timer's refresh rate so that it
* fires fps times per second.
**/
public void setFPS(int fps)
{
int mspf = (int) (1000.0 /fps + 0.5);
timer.setDelay(mspf);
}
/**
* This is the JPanel's timer listener. It runs the example's logic and repaint
* methods each time it gets a timer signal.
**/
private class TimerListener implements ActionListener
{
long startTime = System.currentTimeMillis();
long lastTime = this.startTime;
int ticks = 0;
public void actionPerformed(ActionEvent e)
{
Object source = e.getSource();
if(source == timer)
{
// perform a loop through the game's logic and repaint.
synchronized(this)
{
if(timerReady)
{
timerReady = false;
runSSCCELogic();
repaint();
timerReady = true;
}
}
// Logic for Frames per Second counter
this.ticks++;
long currentTime = System.currentTimeMillis();
if(currentTime - startTime >= 500)
{
prevFPS = 1000.0 * ticks/(1.0*currentTime - startTime);
System.out.println(prevFPS);
startTime = currentTime;
ticks = 0;
}
lastTime = currentTime;
}
}
}
/**
* repaints the SSCCE.
* This just shows the current FPS.
**/
public void paintComponent(Graphics g)
{
super.paintComponent(g);
Graphics2D g2D = (Graphics2D) g;
double roundedFPS = Math.round(prevFPS*10)/10.0;
g2D.setColor(new Color(0x000000));
g2D.drawString("FPS: " + roundedFPS, 20,20);
g.dispose();
}
/**
* runSSCCEELogic()
* This is where the run-time logic for the SSCCE example is.
* All it does is load and play a midi called "mymidi.mid" which is located in the same directory.
**/
public void runSSCCELogic()
{
if(logicLoops == 1)
{
midiPlayer.load("http://www.vgmusic.com/music/computer/microsoft/windows/touhou_6_stage3_boss.mid");
midiPlayer.play(true);
}
logicLoops++;
}
}
/**
* MidiPlayer
* A class that allows midi files to be loaded and played.
**/
class MidiPlayer
{
private Sequence seq;
private Sequencer seqr;
private Synthesizer synth;
private Receiver receiver;
private File midiFile;
private String midiID;
private boolean loaded;
private boolean usingHardwareSoundbank;
// CONSTRUCTORS
public MidiPlayer()
{
loaded = false;
try
{
seqr = MidiSystem.getSequencer();
synth = MidiSystem.getSynthesizer();
}
catch(Exception e)
{
System.out.println("MIDI error: It appears your system doesn't have a MIDI device or your device is not working.");
}
}
/**
* MidiPlayer(String fileName)
* Constructor that also loads an initial midi file.
* Preconditions: fileName is the name of the midi file to be loaded.
* Postconditions: The MidiPlayer is created and loaded with the midi specified by fileName.
**/
public MidiPlayer(String fileName)
{
this();
load(fileName);
}
// DATA METHODS
/**
* load(String fileName)
* loads a midi file into this MidiPlayer.
* Preconditions: fileName is the name of the midi file to be loaded.
* Postconditions: fileName is loaded and is ready to be played.
**/
public void load(String fileName)
{
this.unload();
try
{
URL midiURL = new URL(fileName);
// midiFile = new File(fileName);
seq = MidiSystem.getSequence(midiURL);
seqr.open();
synth.open();
System.out.println("MidiDeviceInfo: ");
for(MidiDevice.Info info : MidiSystem.getMidiDeviceInfo())
{
System.out.println("\t" + info);
}
System.out.println();
if(synth.getDefaultSoundbank() == null)
{
receiver = MidiSystem.getReceiver();
usingHardwareSoundbank = true;
System.out.println("using hardware soundbank");
}
else
{
receiver = synth.getReceiver();
usingHardwareSoundbank = false;
System.out.println("using default software soundbank:" + synth.getDefaultSoundbank());
}
seqr.getTransmitter().setReceiver(receiver);
seqr.setSequence(seq);
loaded = true;
}
catch(IOException ioe)
{
System.out.println("MIDI error: Problem occured while reading " + midiFile.getName() + ".");
}
catch(InvalidMidiDataException imde)
{
System.out.println("MIDI error: " + midiFile.getName() + " is not a valid MIDI file or is unreadable.");
}
catch(Exception e)
{
System.out.println("MIDI error: Unexplained error occured while loading midi.");
}
}
/**
* unload()
* Unloads the current midi from the MidiPlayer and releases its resources from memory.
**/
public void unload()
{
this.stop();
seqr.close();
midiFile = null;
loaded = false;
}
// OTHER METHODS
/**
* setMidiID(String id)
* associates a String ID with the current midi.
* Preconditions: id is the ID we are associating with the current midi.
**/
public void setMidiID(String id)
{
midiID = id;
}
/**
* getMidiID(String id)
*
**/
public String getMidiID()
{
return new String(midiID);
}
/**
* play(boolean reset)
* plays the currently loaded midi.
* Preconditions: reset tells our midi whether or nor to begin playing from the start of the midi file's current loop start point.
* Postconditions: If reset is true, then the loaded midi begins playing from its loop start point (default 0).
* If reset is false, then the loaded midi resumes playing from its current position.
**/
public void play(boolean reset)
{
if(reset)
seqr.setTickPosition(seqr.getLoopStartPoint());
seqr.start();
}
/**
* stop()
* Pauses the current midi if it was playing.
**/
public void stop()
{
if(seqr.isOpen())
seqr.stop();
}
/**
* isRunning()
* Returns true if the current midi is playing. Returns false otherwise.
**/
public boolean isRunning()
{
return seqr.isRunning();
}
/**
* loop(int times)
* Sets the current midi to loop from start to finish a specific number of times.
* Preconditions: times is the number of times we want our midi to loop.
* Postconditions: The current midi is set to loop times times.
* If times = -1, the current midi will be set to loop infinitely.
**/
public void loop(int times)
{
loop(times,0,-1);
}
/**
* loop(int times)
* Sets the current midi to loop from a specified start point to a specified end point a specific number of times.
* Preconditions: times is the number of times we want our midi to loop.
* start is our loop's start point in ticks.
* end is our loop's end point in ticks.
* Postconditions: The current midi is set to loop from tick start to tick end times times.
* If times = -1, the current midi will be set to loop infinitely.
**/
public void loop(int times, long start, long end)
{
if(start < 0)
start = 0;
if(end > seqr.getSequence().getTickLength() || end <= 0)
end = seqr.getSequence().getTickLength();
if(start >= end && end != -1)
start = end-1;
seqr.setLoopStartPoint(start);
seqr.setLoopEndPoint(end);
if(times == -1)
seqr.setLoopCount(Sequencer.LOOP_CONTINUOUSLY);
else
seqr.setLoopCount(times);
}
public void setVolume(double vol)
{
try
{
if(usingHardwareSoundbank)
{
ShortMessage volumeMessage = new ShortMessage();
for ( int i = 0; i < 16; i++ )
{
volumeMessage.setMessage( ShortMessage.CONTROL_CHANGE, i, 7, (int)(vol*127) );
receiver.send( volumeMessage, -1 );
}
}
else
{
MidiChannel[] channels = synth.getChannels();
for( int c = 0; channels != null && c < channels.length; c++ )
{
channels[c].controlChange( 7, (int)( vol*127) );
}
}
}
catch ( Exception e )
{
e.printStackTrace();
}
}
}
The sound quality of MIDI is dependent on the synth generating the sound. It shouldn't have anything to do with your code.
Most likely, it is an issue with your sound card, but that isn't always what generates the sound, especially these days. Under Windows, there is a software synth from Microsoft that does all that. In any case, this won't have anything to do with your code.
It turns out that the problem is with my JRE build. I tried running this simple example for playing midis that Andrew linked me to:
import javax.sound.midi.*;
import java.net.URL;
class PlayMidi {
public static void main(String[] args) throws Exception {
URL url = new URL("http://www.vgmusic.com/music/computer/microsoft/windows/touhou_6_stage3_boss.mid");
Sequence sequence = MidiSystem.getSequence(url);
Sequencer sequencer = MidiSystem.getSequencer();
sequencer.open();
sequencer.setSequence(sequence);
sequencer.start();
}
}
and the midi's sound quality still didn't improve. I ran both my SSCCE and the above minimal example using Java 6_31 and Java 6_32.
In conclusion, this is a problem inherent in Java 6_31 and Java 6_32. So, I guess I'm out of luck until Sun/Oracle releases the next JRE build which will hopefully has this problem resolved.
EDIT:
I just had a friend who has Java 6_31 installed test this out on his machine. He didn't notice any difference in sound quality when he ran my java example vs playing the midi outside of Java. So, it is could also be likely that the problem has something to do with my machine specifically instead of it being a bug in Java. However, another friend just tested it and was also experiencing the same problem I'm having.
In conclusion, the problem is either inherent in Java versions past 6_31, in the sound devices of some machines, or a combination of both. The problem is probably not worth pursuing any further in native java.
I made a change to my MidiPlayer code that seems to have resolved the problem for me. I moved the code where it loads the soundbank and the transmitter's receiver to the constructor instead of in the method where it loads a midi. It no longer sounds really loud and tinny.
package gameEngine;
import javax.sound.midi.*;
import java.io.File;
import java.io.IOException;
import java.net.URL;
/**
* MidiPlayer
* author: Stephen Lindberg
* Last modified: Oct 14, 2011
*
* A class that allows midi files to be loaded and played.
**/
public class MidiPlayer
{
private Sequence seq;
private Sequencer seqr;
private Synthesizer synth;
private Receiver receiver;
private File midiFile;
private String midiID;
private boolean loaded;
private boolean usingHardwareSoundbank;
private float defaultTempo;
// CONSTRUCTORS
public MidiPlayer()
{
loaded = false;
try
{
seqr = MidiSystem.getSequencer();
synth = MidiSystem.getSynthesizer();
// print the user's midi device info
System.out.println("Setting up Midi Player...");
System.out.println("MidiDeviceInfo: ");
for(MidiDevice.Info info : MidiSystem.getMidiDeviceInfo())
{
System.out.println("\t" + info.getName() + ": " +info.getDescription());
}
System.out.println();
// obtain the receiver. This will be used for changing volume.
Soundbank soundbank = synth.getDefaultSoundbank();
if(soundbank == null)
{
receiver = MidiSystem.getReceiver();
usingHardwareSoundbank = true;
System.out.println("using hardware soundbank");
}
else
{
synth.loadAllInstruments(soundbank);
receiver = synth.getReceiver();
usingHardwareSoundbank = false;
System.out.println("using default software soundbank:" + soundbank);
}
seqr.getTransmitter().setReceiver(receiver);
}
catch(Exception e)
{
System.out.println("MIDI error: It appears your system doesn't have a MIDI device or your device is not working.");
}
}
/**
* MidiPlayer(String fileName)
* Constructor that also loads an initial midi file.
* Preconditions: fileName is the name of the midi file to be loaded.
* Postconditions: The MidiPlayer is created and loaded with the midi specified by fileName.
**/
public MidiPlayer(String fileName)
{
this();
load(fileName);
}
// DATA METHODS
/**
* load(String fileName)
* loads a midi file into this MidiPlayer.
* Preconditions: fileName is the name of the midi file to be loaded.
* Postconditions: fileName is loaded and is ready to be played.
**/
public void load(String fileName)
{
this.unload();
try
{
URL midiURL = getClass().getClassLoader().getResource(fileName);
seq = MidiSystem.getSequence(midiURL);
seqr.open();
synth.open();
// load our sequence into the sequencer.
seqr.setSequence(seq);
loaded = true;
defaultTempo = seqr.getTempoInBPM();
}
catch(IOException ioe)
{
System.out.println("MIDI error: Problem occured while reading " + midiFile.getName() + ".");
}
catch(InvalidMidiDataException imde)
{
System.out.println("MIDI error: " + midiFile.getName() + " is not a valid MIDI file or is unreadable.");
}
catch(Exception e)
{
System.out.println("MIDI error: Unexplained error occured while loading midi.");
}
}
/**
* unload()
* Unloads the current midi from the MidiPlayer and releases its resources from memory.
**/
public void unload()
{
this.stop();
seqr.close();
synth.close();
midiFile = null;
loaded = false;
}
// OTHER METHODS
/**
* setMidiID(String id)
* associates a String ID with the current midi.
* Preconditions: id is the ID we are associating with the current midi.
**/
public void setMidiID(String id)
{
midiID = id;
}
/**
* getMidiID(String id)
*
**/
public String getMidiID()
{
return new String(midiID);
}
/**
* play(boolean reset)
* plays the currently loaded midi.
* Preconditions: reset tells our midi whether or nor to begin playing from the start of the midi file's current loop start point.
* Postconditions: If reset is true, then the loaded midi begins playing from its loop start point (default 0).
* If reset is false, then the loaded midi resumes playing from its current position.
**/
public void play(boolean reset)
{
if(reset)
seqr.setTickPosition(seqr.getLoopStartPoint());
seqr.start();
}
/**
* stop()
* Pauses the current midi if it was playing.
**/
public void stop()
{
if(seqr.isOpen())
seqr.stop();
}
/**
* isRunning()
* Returns true if the current midi is playing. Returns false otherwise.
**/
public boolean isRunning()
{
return seqr.isRunning();
}
/**
* getTempo()
* Returns the current tempo of the MidiPlayer in BPM (Beats per Minute).
**/
public float getTempo()
{
return seqr.getTempoInBPM();
}
/**
* loop(int times)
* Sets the current midi to loop from start to finish a specific number of times.
* Preconditions: times is the number of times we want our midi to loop.
* Postconditions: The current midi is set to loop times times.
* If times = -1, the current midi will be set to loop infinitely.
**/
public void loop(int times)
{
loop(times,0,-1);
}
/**
* loop(int times)
* Sets the current midi to loop from a specified start point to a specified end point a specific number of times.
* Preconditions: times is the number of times we want our midi to loop.
* start is our loop's start point in ticks.
* end is our loop's end point in ticks.
* Postconditions: The current midi is set to loop from tick start to tick end times times.
* If times = -1, the current midi will be set to loop infinitely.
**/
public void loop(int times, long start, long end)
{
if(start < 0)
start = 0;
if(end > seqr.getSequence().getTickLength() || end <= 0)
end = seqr.getSequence().getTickLength();
if(start >= end && end != -1)
start = end-1;
seqr.setLoopStartPoint(start);
seqr.setLoopEndPoint(end);
if(times == -1)
seqr.setLoopCount(Sequencer.LOOP_CONTINUOUSLY);
else
seqr.setLoopCount(times);
}
/**
* resetTempo()
* Resets the MidiPlayer's tempo the the initial tempo of its current midi.
**/
public void resetTempo()
{
this.changeTempo(this.defaultTempo);
}
/**
* changeTempo(float bpm)
* Changes the MidiPlayer's current tempo.
* Preconditions: bpm is the MidiPlayer's new tempo in BPM (Beats per Minute).
* Postconditions: The MidiPlayer's current tempo is set to bpm BPM.
**/
public void changeTempo(float bpm)
{
double lengthCoeff = bpm/seqr.getTempoInBPM();
seqr.setLoopStartPoint((long) (seqr.getLoopStartPoint()*lengthCoeff));
seqr.setLoopEndPoint((long) (seqr.getLoopEndPoint()*lengthCoeff));
seqr.setTempoInBPM(bpm);
}
public void setVolume(double vol)
{
System.out.println("Midi volume change request: " + vol);
try
{
if(usingHardwareSoundbank)
{
ShortMessage volumeMessage = new ShortMessage();
for ( int i = 0; i < 16; i++ )
{
volumeMessage.setMessage( ShortMessage.CONTROL_CHANGE, i, 7, (int)(vol*127) );
receiver.send( volumeMessage, -1 );
}
}
else
{
MidiChannel[] channels = synth.getChannels();
for( int c = 0; c < channels.length; c++ )
{
if(channels[c] != null) {
channels[c].controlChange( 7, (int)( vol*127) );
}
}
}
}
catch ( Exception e )
{
e.printStackTrace();
}
}
}