Jump to content

Trigger field activating what it touches? (Music sequencer thread)


Recommended Posts

Posted

I realize this is a bit far out, but I really need some suggestions at this point. I'm trying to make a music sequencer.

 

Basic set-up:

 

  • Blocks (func_usables) stacked vertically to determine pitch (higher up = higher tones, etc).
  • A row of blocks stacked horizontally for each pitch. Starting out with 12 (vertical) x 16 (horizontal) blocks visible at any time (= 192. Is that a problem? 12 tones vertically is very limited. 24 would be the next step)
  • When you shoot them you change the note value (disappear and get replaced by the next block waiting to spawn).
  • Each block has properties corresponding to the note value (6 note values + 1 blank possible for each note).
  • A button that executes the sequence. Every block is played, then it will wait the same amount of time before moving on to the next block. Each block can have a note value or a blank value.

 

I've spent many hours now trying to figure out the best way to script this. Everytime I think I have the best solution, I eventually realize it still requires repulsively many unique scripts (or any other equal unrealistic amount of work). Basically, the part where it has to determine which block is currently visible is boggling my mind.

 

I can't think of any intelligent way to write such a script. Is there a way to check if an entity (like func_usable) is toggled on or off - visible or invisible? Well, even if there is, it would still be a scripting nightmare, I think. I have to potentially take 192 x 7 possibilities into account. 384 x 7 if I'll have my way. Unless I'm getting myself wrong here...

 

 

 

Anyway! Over to the real question here:

 

Instead, I would much much rather have something like a moving trigger that uses the entities it touches. Or something in that alley. Some kind of way to just run a set of entities that are visible. Is that possible? Is there any way around that?

Posted

Sounds like it's way beyond what JA was meant to do... You could write a custom mod for it but I don't think what you want can realistically be done with scripts in base JA.

Posted

How about triggers that trigger multiple entities with different targetnames? I can't make them have the same targetname, otherwise the note value function wouldn't work.

Posted

@@mrwonko: I know using the parm1 key on NPC spawners works, does it work on other entities?

 

I guess you could make a script that is constantly changing some global variables and then one that checks them and plays the sound. I'll have to think about that for a while...

Posted

I dont understand your intention from the first post. If each higher vertical block increases the note pitch, what do you want the blocks moving horizontally to do?

 

That would the progression in the song, or sequence or what to call that. It would basically work like any music program where you input MIDI. You have a grid, and the pitch is determined vertically, the notelength and timing is determined horizontally. DAW example: http://www.jamorigin.com/jo/images/daw-setup/midi-out-fl-studio.png

 

For now, it would be a very short sequence but I suspect that might be the best case scenario.

 

So if this was one note: http://www.twitch.tv/boothand/b/503588956 consisting of 8 possible repetitions, and 6 (+1) possible lengths, blocks over and under it would be other pitches. For now there's just one row.

Posted

@@mrwonko: I know using the parm1 key on NPC spawners works, does it work on other entities?

 

I guess you could make a script that is constantly changing some global variables and then one that checks them and plays the sound. I'll have to think about that for a while...

 

Parms can be set on anything. But part of the issue is that every time the note value changes, so does the entity.

 

Maybe it's possible I could discard the idea of having a new entity for each note block, and rather have only the texture be swapped and parms set up to play new wav files. It would simplify things...

therfiles likes this
Posted

What do you mean by changing the note "value" then?

 

I just didnt understand exactly what you want this grid to do and how you want it to work.

 

Yeah, sorry for being a bit vague there.

 

The word value corresponds to a mathematical value, representing the length or duration of a note. Whole notes (the greatest value) last for 4 "beats" or "counts". Then half, quarter notes, eight-notes are just halves of the durations before. 1, 0,5, 0.25, 0,125 could be a way to represent it in for example seconds. Hope that clears up the word "value" in this context.

 

So if you imagine that a script starts on the first row of vertical blocks, the entire chunk from the bottom-most left block to the upper-most left block, (1 of 16 horizontal rows) and plays the sound corresponding to the value those notes are set to (via parms and a script, I suppose). Maybe 3 blocks in that first row would be set to a note value, and the rest would just be blank and play no sound. Say the notes G, B and D were set to quarter notes. The script would then play those at the same time, then after a wait time, it would move on to the second row of vertical blocks and do the same thing for whatever there might be.

 

So the reason for changing the note values is really just to make the tones last longer or shorter. It won't change the time before the script plays the next row of blocks. Granted, most apps/mods etc don't take into account how long the notes should last. For example Minecraft note blocks, or playable piano-mods in games like... Garry's Mod.

 

 

The end result of this would be that the players shoot blocks to make tunes, then execute the sequence by pressing a button where it then plays it from left to right, and possibly as a bonus can choose whether it should loop or not.

Posted

Update on progress: I've managed to switch texture on the blocks every time they are shot. Done by using the "endframe" key. So this way I don't need to deal with hidden entities.

Posted

can you give us a screenshot maybe?  I might have an idea but I don't know exactly what you mean.  An illustration would be great.

 

I am thinking usables and relays or delays could get the effect you want.

Posted

This would be the best illustration I can give for now: http://onlinesequencer.net/

Throw some notes in there, and then click on the play button in the lower left.

 

I don't think I will need relays nor delays. I've thought of it a bit, and will keep it in mind though.

 

 

 

Currently I'm setting it up like this - one script being used when you shoot and set up your sequence, and another script for playing the sequence when it's done:

 

The first script checks this:

If entity gets shot/used AND parm1 = 0
    set parm1 to 1
    play sound (get parm11) (so in this entity, I would set parm11 to the path of the whole-note wave file)

If entity gets shot AND parm1 = 1
    set parm1 to 2
    play sound (get parm12)

etc until

If entity gets shot AND parm1 = 4
   set parm1 to 0
     [play no sound]

This should correspond with the "endframe" key I've set up which changes the texture every time you shoot it.

 

 

The next script will play it when you press a trigger, and does this:

Affect ROW1 (all the blocks in the first vertical row would have the same script_targetname and thus be executed together. I've had an experience where the game picks only one of the entities regardless of identical targetnames though.)
MoonDog, help!

    If parm1 = 0
         do nothing (how do I tell it to do nothing actually? Flush...?)
    If parm1 = 1
        play sound (get parm11)
    If parm1 = 2
        play sound (get parm12) etc with 3 and 4 as well... then

    wait (get amount of time to wait from some other entity in the map that the player can affect, so that they can change the tempo of songs)

Challenge (?): Then I would have to let the script run on every entity without *using* them, because using them has the same effect as when you shoot them and set up the sequencer in the first script.

 

@@MoonDog, could you confirm if I'm onto it here, or if there's something, for example the last part I mentioned, which renders this totally undoable?

Posted

I don't know the inner workings of Icarus in the codebase, but I'm pretty sure affect isn't going to put all entities with the same script_targetname in an array for you and run logic on all of them. 

 

I'd need a coder to clarify on that point. 

 

I'm also pretty sure that you are only allowed 8 total parms on an entity. I'm not entirely 100 percent on that one either. It was just something I was told when I started modding JKA, and I was never shown any of the coding behind that. But it's a safe assumption to work off of.

 

You might have better luck using global variables so you don't need to rely on trying to pick parms off of individual entities.

 

 

For instance, this could be a script for one of the blocks.

//Generated by BehavEd

rem ( "This is just to show what it looks like to declare variables" );
//(BHVDREM)  rem ( "You'd usually put all your declares in a separate scipt that is ran when the map starts" );
declare ( /*@DECLARE_TYPE*/ FLOAT, "block1_note" );
declare ( /*@DECLARE_TYPE*/ FLOAT, "block2_note" );
declare ( /*@DECLARE_TYPE*/ FLOAT, "block3_note" );
declare ( /*@DECLARE_TYPE*/ FLOAT, "block4_note" );
declare ( /*@DECLARE_TYPE*/ FLOAT, "block5_note" );
declare ( /*@DECLARE_TYPE*/ FLOAT, "block6_note" );
declare ( /*@DECLARE_TYPE*/ FLOAT, "block7_note" );
declare ( /*@DECLARE_TYPE*/ FLOAT, "block8_note" );
//(BHVDREM)  rem ( "SPACING_FUNCTIONS!" );
//(BHVDREM)  rem ( "SPACING_FUNCTIONS!" );
//(BHVDREM)  rem ( "SPACING_FUNCTIONS!" );

if ( $get( FLOAT, "SET_PARM1")$, $=$, $0$ )
{
	set ( /*@SET_TYPES*/ "SET_PARM1", "+1" );
	set ( "block1_note", $get( FLOAT, "SET_PARM1")$ );
	print ( $get( FLOAT, "SET_PARM1")$ );
}


else (  )
{

	if ( $get( FLOAT, "SET_PARM1")$, $=$, $1$ )
	{
		set ( /*@SET_TYPES*/ "SET_PARM1", "+1" );
		set ( "block1_note", $get( FLOAT, "SET_PARM1")$ );
		print ( $get( FLOAT, "SET_PARM1")$ );
	}


	else (  )
	{

		if ( $get( FLOAT, "SET_PARM1")$, $=$, $2$ )
		{
			set ( /*@SET_TYPES*/ "SET_PARM1", "+1" );
			set ( "block1_note", $get( FLOAT, "SET_PARM1")$ );
			print ( $get( FLOAT, "SET_PARM1")$ );
		}


		else (  )
		{

			if ( $get( FLOAT, "SET_PARM1")$, $=$, $3$ )
			{
				set ( /*@SET_TYPES*/ "SET_PARM1", "+1" );
				set ( "block1_note", $get( FLOAT, "SET_PARM1")$ );
				print ( $get( FLOAT, "SET_PARM1")$ );
			}


			else (  )
			{

				if ( $get( FLOAT, "SET_PARM1")$, $>=$, $4$ )
				{
					set ( /*@SET_TYPES*/ "SET_PARM1", "0" );
					set ( "block1_note", $get( FLOAT, "SET_PARM1")$ );
					print ( $get( FLOAT, "SET_PARM1")$ );
				}

			}

		}

	}

}

Then instead of having to depend on grabbing variables off of the entities, you could read the global variables without running any affect blocks. For this, you'd probably have to setup a script for each block with multiple scriptrunners. Target the trigger_multiple at a target_relay and target the relay at all the scriptrunners.

//Generated by BehavEd


if ( $get( FLOAT, "block1_note")$, $=$, $0$ )
{
	sound ( /*@CHANNELS*/ CHAN_AUTO, "null" );
}


else (  )
{

	if ( $get( FLOAT, "block1_note")$, $=$, $1$ )
	{
		sound ( /*@CHANNELS*/ CHAN_AUTO, "my_fruity_sound1.mp3" );
	}


	else (  )
	{

		if ( $get( FLOAT, "block1_note")$, $=$, $2$ )
		{
			sound ( /*@CHANNELS*/ CHAN_AUTO, "my_fruity_sound2.mp3" );
		}


		else (  )
		{

			if ( $get( FLOAT, "block1_note")$, $=$, $3$ )
			{
				sound ( /*@CHANNELS*/ CHAN_AUTO, "my_fruity_sound3.mp3" );
			}


			else (  )
			{

				if ( $get( FLOAT, "block1_note")$, $=$, $4$ )
				{
					sound ( /*@CHANNELS*/ CHAN_AUTO, "my_fruity_sound4.mp3" );
				}

			}

		}

	}

}

Posted

Thanks! Ok, so basically passing on states to global variables, during sequencing, and then during playback you check the states in each block and play the corresponding sound.

Hmm. The disadvantage I (think I) see, is that you'd need such a script for every available tone. Say 24 scripts. It could work. But hmm. I would probably want to re-use this script for the horizontal notes, and that kind of leaves me wondering. How would it know that it should execute a whole row of blocks at a time, not horizontally, but vertically? I'm worried that it would play at once the sounds of all the instances of "block1_note", which would be all the notes from left to right of that particular tone. I don't know how to go around it without doing what I feared, which is to make a script for every block, which would never be worth it.

 

I really hope I could get away with using 9 or 10 parms on each entity. BehavEd has 16 available parms though. The reason why I used parms 10-14 was that it corresponded visually with parm1 = 0-4.

 

 

 

I don't know the inner workings of Icarus in the codebase, but I'm pretty sure affect isn't going to put all entities with the same script_targetname in an array for you and run logic on all of them. 

 

I'd need a coder to clarify on that point. 

 

 

Any coders who could clarify on this?  @@mrwonko, @@eezstreet?

Would be fantastic if that worked, or something similar worked.

 

I'll see if I can do something with global variables if my plans above are foiled.

 

EDIT: But I'm not using more than 8 parms am I? I just realized, I'm only using parm1 for setting 0-4. Then I'm using 5 (maybe 4 actually) other parms to hold paths to sounds.

Posted

I'm just showing you how I would do it. Global variables. As for timing, other directions, order, etc... you'd need to iterate on your own until you find a way to make it work which makes you happy.

 

This is Icarus. Icarus is weak.

Boothand likes this
Posted

Ok, ran into a little bump here. This would be a problem no matter what method I use, I think.

 

I'm in the process of setting up my first script, according to how I planned it in this post.

 

Just when I add the usescript to any of my usables, I get a crash when starting up the map. I've tested the sounds themselves, and they run in-game.

 

Here is the script:

 

 

 

 

 

//Generated by BehavEd


rem ( "0 = none, 1 = whole, 2 = half, 3 = quarter, 4 = eight" );


if ( $get( FLOAT, "SET_PARM1")$, $=$, $0$ )
{
set ( /*@SET_TYPES*/ "SET_PARM1", "1" );
sound ( /*@CHANNELS*/ CHAN_AUTO, $get( STRING, "SET_PARM11")$ );
print ( $get( FLOAT, "SET_PARM1")$ );
}




else (  )
{


if ( $get( FLOAT, "SET_PARM1")$, $=$, $1$ )
{
set ( /*@SET_TYPES*/ "SET_PARM1", "2" );
sound ( /*@CHANNELS*/ CHAN_AUTO, $get( STRING, "SET_PARM12")$ );
print ( $get( FLOAT, "SET_PARM1")$ );
}


}




else (  )
{


if ( $get( FLOAT, "SET_PARM1")$, $=$, $2$ )
{
set ( /*@SET_TYPES*/ "SET_PARM1", "3" );
sound ( /*@CHANNELS*/ CHAN_AUTO, $get( STRING, "SET_PARM13")$ );
print ( $get( FLOAT, "SET_PARM1")$ );
}


}




else (  )
{


if ( $get( FLOAT, "SET_PARM1")$, $=$, $3$ )
{
set ( /*@SET_TYPES*/ "SET_PARM1", "4" );
sound ( /*@CHANNELS*/ CHAN_AUTO, $get( STRING, "SET_PARM14")$ );
print ( $get( FLOAT, "SET_PARM1")$ );
}


}




else (  )
{


if ( $get( FLOAT, "SET_PARM1")$, $=$, $4$ )
{
set ( /*@SET_TYPES*/ "SET_PARM1", "0" );
print ( $get( FLOAT, "SET_PARM1")$ );
}


}

 

 

 

 

 

And here is the error: utilsError.png

 

And here is what g_utils.c says:

 

 

 

int G_SoundIndex( const char *name ) {
	assert(name && name[0]);
	return G_FindConfigstringIndex (name, CS_SOUNDS, MAX_SOUNDS, qtrue);
}


 

 

 

 

Entity window:

 

 

 

entitywindow.png

 

 

 

Posted

You're passing in a blank parm somewhere, causing it to assert.

 

Hmm. It seems that *getting* the sounds through the parms is causing it at least. Replacing it with the path of the sounds fixes the issue, although a disadvantage. A temporary solution.

 

I'm having a different problem now. Using only *if*, it will execute all the if statements in one go, because they become true immediately, instead of waiting until next time the script is run.

 

Using *else if* the way @@MoonDog set it up gives me an "invalid else found!" error in-game, and doesn't execute the script any further.

//Generated by BehavEd

rem ( "0 = none, 1 = whole, 2 = half, 3 = quarter, 4 = eight" );

if ( $get( FLOAT, "SET_PARM1")$, $=$, $0$ )
{
	set ( /*@SET_TYPES*/ "SET_PARM1", "1" );
	sound ( /*@CHANNELS*/ CHAN_AUTO, "sound/bootland_mp/sequencer/C3_whole.wav" );
}


else (  )
{

	if ( $get( FLOAT, "SET_PARM1")$, $=$, $1$ )
	{
		set ( /*@SET_TYPES*/ "SET_PARM1", "2" );
		sound ( /*@CHANNELS*/ CHAN_AUTO, "sound/bootland_mp/sequencer/C3_half.wav" );
	}

}


else (  )
{

	if ( $get( FLOAT, "SET_PARM1")$, $=$, $2$ )
	{
		set ( /*@SET_TYPES*/ "SET_PARM1", "3" );
		sound ( /*@CHANNELS*/ CHAN_AUTO, "sound/bootland_mp/sequencer/C3_quarter.wav" );
	}

}


else (  )
{

	if ( $get( FLOAT, "SET_PARM1")$, $=$, $3$ )
	{
		set ( /*@SET_TYPES*/ "SET_PARM1", "4" );
		sound ( /*@CHANNELS*/ CHAN_AUTO, "sound/bootland_mp/sequencer/C3_eight.wav" );
	}

}


else (  )
{
	set ( /*@SET_TYPES*/ "SET_PARM1", "0" );
}


 

 

From https://github.com/zachlatta/jedi-academy/blob/master/codemp/icarus/Sequencer.cpp

if ( m_elseOwner == NULL )
	{
		m_ie->I_DPrintf( WL_ERROR, "Invalid 'else' found!\n" );
		return SEQ_FAILED;
	}

 

 

Posted

@@Boothand: It seems to me like a lot of your "ELSE" blocks are not within the original "ELSE".

 

Take a look at MoonDog's example:



if ( $get( FLOAT, "block1_note")$, $=$, $0$ )
{
	sound ( /*@CHANNELS*/ CHAN_AUTO, "null" );
}


else (  )
{

	if ( $get( FLOAT, "block1_note")$, $=$, $1$ )
	{
		sound ( /*@CHANNELS*/ CHAN_AUTO, "my_fruity_sound1.mp3" );
	}


	else (  )
	{

		if ( $get( FLOAT, "block1_note")$, $=$, $2$ )
		{
			sound ( /*@CHANNELS*/ CHAN_AUTO, "my_fruity_sound2.mp3" );
		}


		else (  )
		{

			if ( $get( FLOAT, "block1_note")$, $=$, $3$ )
			{
				sound ( /*@CHANNELS*/ CHAN_AUTO, "my_fruity_sound3.mp3" );
			}


			else (  )
			{

				if ( $get( FLOAT, "block1_note")$, $=$, $4$ )
				{
					sound ( /*@CHANNELS*/ CHAN_AUTO, "my_fruity_sound4.mp3" );
				}

			}

		}

	}

}

See how the first IF is followed by the ELSE? The next if and else blocks should be in that ELSE.

 

For example. IF Else>(IF ELSE>(IF ELSE>))

 

Make sense?

 

Another example:

 

IF (something1)

 actions

ELSE

 IF (something2)

  actions

 ELSE

  IF (something3)

   actions

  ELSE

   etc

Boothand likes this
Posted

Ahh! Of course. Will try this at once, it makes very much sense. Thanks!

 

Yep, works!

 

Now I gotta find out how to get the sound-file through parms without getting that assertion error thing...

Posted

Progress update:

 

Confirmed that affecting more than one entity with same script_targetname doesn't work. It picks one of them, in this case the most recently added ones. Any workaround for this is greatly appreciated!

 

I'm kind of stuck before I solve the "Assertion failed!" error. I get this error everytime I try to *get* the path to a soundfile through one of the parms. If this worked properly, it would resolve a whole lot of things.

Eezstreet said I was passing a blank parm somewhere, but I've narrowed it down and found that this happens even when I add only instance of "sound *get parmX". The alternative seems to be to add each sound in the script, which is very inefficient!

 

I think I'm gonna summon some more people for this one! @@eezstreet, @@Raz0r, @@Xycaleth. Tag anyone who might know.

 

Here's the pictures again:

 

utilsError.png                         entitywindow.png

 

And the code snippet regarding this error:

 

 

 

int G_SoundIndex( const char *name ) {
	assert(name && name[0]);
	return G_FindConfigstringIndex (name, CS_SOUNDS, MAX_SOUNDS, qtrue);
}

 

 

Posted

As you're using a debug build, is there any way you can debug with Visual Studio and get a call-stack when the assert hits? (i.e. click 'break', which i assume is 'avbryt')

Posted

As you're using a debug build, is there any way you can debug with Visual Studio and get a call-stack when the assert hits? (i.e. click 'break', which i assume is 'avbryt')

 

(Yeah, cancel, try again or ignore)

 

If I press cancel, it just closes. It doesn't give me the option to debug, which I've seen in other crashes before, if this is what you meant. If you wanted me to do something with Visual Studio and code, I would need very specific instructions, heh.

 

However!

I have a bit mixed feelings now. Clicking ignore actually takes me in-game, and it works perfectly. It gets the parm properly with no console errors. But it's a must-fix of course even though it works.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×
×
  • Create New...