This article relates to the game "Portal 2". Click here for more information.

Creating custom Portal 2 funnel music

From Valve Developer Community
Jump to navigation Jump to search
Icon-under construction-blue.png
This is a draft page. It is a work in progress open to editing by anyone.
Remember to check for any notes left by the tagger at this article's talk page.

For more info on Soundscripts and Sound Operators, see Soundscripts.

Stub

This article or section is a stub. You can help by expanding it.

This article will cover the creation of custom Soundscripts that will enable custom funnel/tractor beam music to be played in tandem with your custom music in Portal 2 Portal 2 levels.

Overview

In Portal 2, chambers that have Tractor Beams/Excursion Funnels, or simply funnels, contain a custom soundtrack that plays whenever a player enters a funnel. This logic is contained within the soundscript used for the music in the chamber, which uses Sound Operators to enable these tracks. In this article, you will learn how to create your own soundscript that will toggle between your normal music and your funnel music.

Tutorial

Soundscript Creation

To begin, navigate to 🖿<steam dir>\steamapps\Portal 2\portal2\scripts and copy 🖿game_sounds_music.txt. Paste this in 🖿Portal 2\portal2_dlc3\scripts (if you do not have a 🖿portal2_dlc3 folder, make one).

Tip.pngTip:For Portal 2: Community Edition/Portal: Revolution, the game already registers any new soundscript files on startup, so you can just create your own file and name it game_sounds_music_<yournamehere>.txt. Just paste the required code on the first line.

Now, open this file, and navigate to this line:

// // triple laser
// "music.laser_node_01.play"
// {
// 	"channel"		"CHAN_STATIC"
// 	"soundlevel"	"SNDLVL_70dB"
// 	"volume"		"1.0"

Now, copy and paste this code above // // triple laser:

//
// your_level_name_here
//
"music.your_level_name_here_tbin" // Funnel music
{
	"channel"		"CHAN_STATIC"
	"soundlevel"	"SNDLVL_NONE"
	"volume"		"0.8"


	"wave"	"*music/your_tbmusic_here.wav" // Your funnel music


	"soundentry_version" "2"
	"operator_stacks"
	{
		"start_stack"
		{
			// Random Offset logic
			"random_offset"
			{
				"operator" "math_random"
				"input_min" "0.0"
				"input_max" "126"
			}

			"negative_delay"
			{
				"operator" "math_float"
				"apply" "mult"	
				"input1" "@random_offset.output"
				"input2" "-1.0"	
			}
			"delay_output"
			{
				"operator" "sys_output"
				"input_float" "@negative_delay.output"
				"output" "delay"
			}
		}

		"update_stack"
		{
			"import_stack" 	"update_music_stereo"
			"mixer"
			{
				"mixgroup" "unduckedMusic"
			}

			"volume_fade_in"
			{
				"input_max" "3.0"
				"input_map_min" "0.05"
			}
			"volume_fade_out"
			{
				"input_max" "0.75"
				"input_map_min" "0.05"
			}
			"volume_lfo_time_scale"
			{
				"input2" "0.3"	
			}
			"volume_lfo_scale"
			{
				"input2" "0.4"
			}

		}
	}
}

"music.your_level_name_here_b1" // Normal music
{
	"channel"		"CHAN_STATIC"
	"soundlevel"	"SNDLVL_NONE"
	"volume"		".8"


	"wave"	"*music/your_music_here.wav" //Your normal music


	"soundentry_version" "2"
	"operator_stacks"
	{
		"update_stack"
		{
			"import_stack" 	"update_music_stereo"
			"volume_fade_out"
			{
				"input_max" "3.0"
				"input_map_min" "0.05"
			}

			"import_stack" "p2_update_music_play_tbeam"
			"play_entry"
			{
				"entry_name" "music.your_level_name_here_tbin"
			}	
			"stop_entry"
			{
				"match_entry" "music.your_level_name_here_tbin"
			}
		}
	}
}

Use your text editing program's find and replace function to replace "your_level_name_here" with your level name, then replace "your_tbmusic_here" and "your_music_here" with the paths to your funnel and normal music, respectively. Now, save this file.

Optional: Syncronize the funnel and main music

The soundscript provided above by default starts the funnel music at a random time. If your tracks perfectly match up and would like your funnel music to be synced up with your main music in-game, you will need to replace the logic managing the randomized offset with synchronization logic in its place.
Locate these lines containing the "start_stack" section in the first soundscript:

"soundentry_version" "2"
	"operator_stacks"
	{
		"start_stack"
		{
			// Random Offset logic
			"random_offset"
			{
				"operator" "math_random"
				"input_min" "0.0"
				"input_max" "126"
			}

			"negative_delay"
			{
				"operator" "math_float"
				"apply" "mult"	
				"input1" "@random_offset.output"
				"input2" "-1.0"	
			}
			"delay_output"
			{
				"operator" "sys_output"
				"input_float" "@negative_delay.output"
				"output" "delay"
			}
		}

		"update_stack"
		{
		...

Now, copy the following code and paste it over the above highlighted code:

		"start_stack"
		{
            "catch_entry" { //Used to catch the entry we're syncing to
                "entry" "music.your_level_name_here_b1"
			    "operator" "get_entry_time"
		    }


		    "sound_offset" { // We have to account for the multiple loops that can happen
		    	"operator" "math_float"
		    	"apply" "mod"
		    	"input1" "@catch_entry.output_entry_elapsed" //Get the duration this sound has been playing for
		    	"input2" "@catch_entry.output_sound_duration" // % it by the total duration of the file we're playing from
		    }

		    "invert_offset" { //We have to skip the duration, the delay paramater accepts negative values to do that
		    	"operator" "math_float"
		    	"apply" "mult"
		    	"input1" "-1"
		    	"input2" "@sound_offset.output"
		    }

		    "whoosh" { //Skip the seconds to sync
		    	"operator" "sys_output"
		    	"output" "delay"
		    	"input_float" "@invert_offset.output"
		    }
		}

Now, it should look like this:

"soundentry_version" "2"
	"operator_stacks"
	{
		"start_stack"
		{
            "catch_entry" { //Used to catch the entry we're syncing to
                "entry" "music.your_level_name_here_b1"
			    "operator" "get_entry_time"
		    }


		    "sound_offset" { // We have to account for the multiple loops that can happen
		    	"operator" "math_float"
		    	"apply" "mod"
		    	"input1" "@catch_entry.output_entry_elapsed" //Get the duration this sound has been playing for
		    	"input2" "@catch_entry.output_sound_duration" // % it by the total duration of the file we're playing from
		    }

		    "invert_offset" { //We have to skip the duration, the delay paramater accepts negative values to do that
		    	"operator" "math_float"
		    	"apply" "mult"
		    	"input1" "-1"
		    	"input2" "@sound_offset.output"
		    }

		    "whoosh" { //Skip the seconds to sync
		    	"operator" "sys_output"
		    	"output" "delay"
		    	"input_float" "@invert_offset.output"
		    }
		}

		"update_stack"
		{
		...

Now, your music should sync up perfectly when you exit and enter the funnel. Remember, this only makes sure the sounds run synchronously and does not do any waveform matching. If your sounds do not perfectly match up, or at least close enough, there will be a noticeable difference when switching between the sounds.

Selecting the file in Hammer

Now, you will need to play the sound in-game. Open your map in Hammer Hammer and create an ambient_generic, then give it these properties:

! Property Name Value
Name (targetname) main_music_1
Sound Name (message) music.<your_level_name_here>_b1

Remember to use the "b1" soundscript, as this contains the actual logic for the toggle. Now for spawnflags, make sure that "Infinite Range" is toggled and "Is NOT Looped" is untoggled. You can optionally have this sound be enabled by default, or you can have an activator (e.g. a trigger_once).

Conclusion

If you followed this tutorial well enough, you should now have your own soundscript that automatically changes between funnel and main music upon entering and exiting a funnel. Remember when sharing your map to pack this file using BSPZIP or a similar program, so it plays correctly for others! Refer to the Soundscripts and Sound operators pages for documentation on the different operators and functions of Soundscripts if you would like to customize this logic further.