// plusMusic.mo3 has several channels we're going to be manipulating:
//
// Channels 8-9: Waterfall noise (left and right channels).
// Channel 15: Oboe, played when the player is far above ground.
// Channel 16: Bells, played when the player is underground.
//
// These values are 1-indexed, which is how most trackers display them. However,
// internally, channels are 0-indexed, so we pass a value of 6 to access channel
// 7, for example.
enum channels {
	WATER_LEFT = 7,
	WATER_RIGHT = 8,
	OBOE = 14,
	BELLS = 15
} 

enum musicStates {
	ABOVEGROUND,
	GROUND,
	UNDERGROUND
}

const int MUSIC_FADE = 2000; // Duration of fade in milliseconds.

const float WATERFALL_CENTER = 84*32; // The X position of the waterfall.

// The maximum distance at which the waterfall can be heard.
const float WATERFALL_FALLOFF_DISTANCE = 8*32;

// We mostly use Text objects to change the music, but we want to immediately
// fade the appropriate channels when the player respawns at a checkpoint.
const float ABOVEGROUND_HEIGHT = 1920;
const float UNDERGROUND_HEIGHT = 3200;

int musicState = -1;

// Begin by muting all the special channels.
void onLevelLoad() {
	setMusicState(GROUND);
	jjSlideModChannelVolume(WATER_LEFT, 0.0, MUSIC_FADE);
	jjSlideModChannelVolume(WATER_RIGHT, 0.0, MUSIC_FADE);
}

// In single player, update the music after we die.
void onLevelReload() {
	float y = jjLocalPlayers[0].yPos;
	if (y < ABOVEGROUND_HEIGHT) {
		setMusicState(ABOVEGROUND);
	} else if (y > UNDERGROUND_HEIGHT) {
		setMusicState(UNDERGROUND);
	} else {
		setMusicState(GROUND);
	}
	jjSlideModChannelVolume(WATER_LEFT, 0.0, MUSIC_FADE);
	jjSlideModChannelVolume(WATER_RIGHT, 0.0, MUSIC_FADE);
}

// Used when we exit the aboveground or underground areas.
void onFunction0(jjPLAYER@ play) {
	setMusicState(GROUND);
	setBgLayersVisible(true);
}

// Used when we enter the aboveground area.
void onFunction1(jjPLAYER@ play) {
	setMusicState(ABOVEGROUND);
}

// Used when we enter the underground area.
void onFunction2(jjPLAYER@ play) {
	setMusicState(UNDERGROUND);
	setBgLayersVisible(false);
}

// Used when we're about to fight the boss. This pattern makes the music screech
// to a halt, in preparation for the impending switch to the boss music.
void onFunction3(jjPLAYER@ play) {
	jjSetModPosition(11,0,false);
}

// Used when you meet the boss. Loads a music file other than the standard
// pair of boss .j2b files available to the Activate Boss event.
void onFunction4(jjPLAYER@ play) {
	play.activateBoss();
	jjMusicLoad("dang.j2b");
}

// Fade in and out channels as appropriate for the new elevation.
void setMusicState(int newState) {
	if (musicState != newState) {
		musicState = newState;
		switch (musicState) {
			case ABOVEGROUND:
				jjSlideModChannelVolume(OBOE, 1.0, MUSIC_FADE);
				jjSlideModChannelVolume(BELLS, 0.0, MUSIC_FADE);
				break;
			case GROUND:
				jjSlideModChannelVolume(OBOE, 0.0, MUSIC_FADE);
				jjSlideModChannelVolume(BELLS, 0.0, MUSIC_FADE);
				break;
			case UNDERGROUND:
				jjSlideModChannelVolume(OBOE, 0.0, MUSIC_FADE);
				jjSlideModChannelVolume(BELLS, 1.0, MUSIC_FADE);
				break;
			default:
				break;
		}
	}
}

// Finally, this function handles the noise from the waterfall. This is an
// example of a situation where you must account for the buffer: changes in
// volume, tempo, et cetera do not apply instantly. Of the JJ2+ sound functions
// at the time of writing, only jjModSetPosition takes effect instantly, and the
// others will be delayed by (at the time of writing) 500ms.
void onMain() {
	// Only update every 8th frame. Also, we aren't even playing our custom music
	// if the boss is activated, so don't bother updating then, either.
	if (!jjLocalPlayers[0].bossActivated && (jjRenderFrame & 0x00000008 == 0x00000008)) {
		// Unlike the rest of the level, this code accounts for splitscreeners.
		// It calculates the appropriate volume for every player, and uses only
		// the loudest one for each channel.
		float leftVolume = 0.0;
		float rightVolume = 0.0;
		for (int i = 0; i < jjLocalPlayerCount; i++) {
			float x = jjLocalPlayers[i].xPos;
			float rVol, lVol;
			// The volume falls off with distance, and the falloff is much
			// greater for the left channel if you are on the right of the
			// waterfall, or the right channel if you are on the left of the
			// waterfall. 
			if (x >= WATERFALL_CENTER) {
				lVol = 1.0 + (WATERFALL_CENTER - x)/WATERFALL_FALLOFF_DISTANCE;
				rVol = 1.0 + (WATERFALL_CENTER - x)/(WATERFALL_FALLOFF_DISTANCE/4);
			} else {
				lVol = 1.0 - (WATERFALL_CENTER - x)/(WATERFALL_FALLOFF_DISTANCE/4);
				rVol = 1.0 - (WATERFALL_CENTER - x)/WATERFALL_FALLOFF_DISTANCE;
			}

			// Use only the highest volumes for each channel, because of splitscreen.
			// This happens to get rid of volumes below 0.0 as well.
			if (lVol > leftVolume) leftVolume = lVol;
			if (rVol > rightVolume) rightVolume = rVol;
		}
		// Now apply the new volume levels. A delay of 114 ms will match up very
		// closely with the next 8th render frame.
		jjSlideModChannelVolume(WATER_LEFT, leftVolume, 114);
		jjSlideModChannelVolume(WATER_RIGHT, rightVolume, 114);
	}
}

void setBgLayersVisible(bool visible) {
	for (int i = 5; i < 8; i++) {
		jjLayerHasTiles[i] = visible;
	}
}
