Client Sound Mods(?)

Forum for alternative clients, mods & discussions on the same.

Moderator: Phades

User avatar
boshaw
Posts: 1612
Joined: Tue Jun 01, 2010 10:22 pm

Re: Client Sound Mods(?)

Post by boshaw »

It has to be possible at some level, but maybe not with the way the current client works though. Although I do remember the destroy chopping sound being overlapped when multiple people were doing it. Those were at the same volume level, though.
good point, i do recall this as well. I'll look more into the Audio class.



I was gonna make this an edit but i took to long lol: Ideas on how stuff is triggered:
drop item on ground
would be invoked when you ctrl-right click an item (Item.java)
open container
Could be invoked when client see the change in an image sprite to the open status of any of the containers
open/close gate
same as above
catch fish
everytime an `Item.java' widget is added to your `Inventory.java' this could be trigger if item is type fish
FAiled to catch fish
change in character sprite + no catch fish

crawl,walk,run,sprint:....
Every time your character changes position it could easily check to see what tile type it's standing on along with current speed which would then trigger an audio clip
swimming
basically same as above but for DEEP_WATER tiles
drowning
IF client see drown sprite then it triggers audio for it
teleport
Menugrid.java triggers audio if you use teleport pagina
summoned
not sure how you might approach this one without it being spammed when it shouldn;t
log on
triggered after MapView.java widget is created
wake up
same as above + checks to see if your character is located ontop of a bed-type object

gain kin
triggered when BuddyWnd.java adds a new name to the list after server sends it to you
kin request denied
mmm could be triggered after the "Waiting..." window disappears and no new kin after X seconds
prayer, ancestor stuff
Not to sure since i never really used them
belief slider ready
triggered when CharWnd.java btime becomes 0 due to server uimsg send
incoming pm
triggered through new lines added to LineEdit or w/e in the ChatWnd.java classes or one of the chat java classes i forget which one
someone said something

triggered when the client initially gets the request to draw those chat bubbles or could be implemented like the above ^
system message
I forget what those windows look like but you could trigger them if you had a way to tell if it was a system message ui window when it's created

wild death
triggered on death sprite for an animal after it was just in your Fightview Avatars
animal movement
triggered when the animal Gob moves its position: see player movement






I figure i'd edit this in as well:
Process of how audio/music clips are invoked:
refer to : https://github.com/dolda2000/haven-clie ... ssion.java Session.java
Constants: [1 byte, head of message]
  • Message.RMSG_SFX === AUDIO
    Message.RMSG_MUSIC == MUSIC

Code: Select all

//AUDIO
if(msg.type == Message.RMSG_SFX) {
  Indir<Resource> res = getres(msg.uint16());     //Finds the resource file based on 2 byte ID
  double vol = ((double)msg.uint16()) / 256.0;   //Volume, but it isn't used anyway. Possible sign that different volumes per audio clip isn't implemented but was planned!!!
  double spd = ((double)msg.uint16()) / 256.0;  //Not sure what this is, but not used as well anyway
  Audio.play(res);
}

Code: Select all

/MUSIC
if(msg.type == Message.RMSG_MUSIC) {
  String resnm = msg.string();       //Res name
  int resver = msg.uint16();          //Res Version
  boolean loop = !msg.eom() && (msg.uint8() != 0); //loop or not
  if(Music.enabled) {
   if(resnm.equals("")) //this turns off any music already playing i suspect
    Music.play(null, false);
   else //Below starts up a new music clip
    Music.play(Resource.load(resnm, resver), loop);
  }
}
User avatar
Onionfighter
Posts: 2957
Joined: Sat May 30, 2009 8:45 am
Location: Mordor

Re: Client Sound Mods(?)

Post by Onionfighter »

boshaw wrote:
drop item on ground
would be invoked when you ctrl-right click an item (Item.java)
I was hoping this would make a noise for anyone who happened to be nearby (similar to being able to hear stuff being dropped in water). Linking the trigger to the appearance of an item sprite might make it hard to distinguish between something that was dropped and something that appeared as you changed areas or moved around.
boshaw wrote:
open container
Could be invoked when client see the change in an image sprite to the open status of any of the containers
open/close gate
same as above
I was thinking the same.
boshaw wrote:
crawl,walk,run,sprint:....
Every time your character changes position it could easily check to see what tile type it's standing on along with current speed which would then trigger an audio clip
I was thinking more along the lines of being able to hear other characters. I guess it would be more complicated to determine the speed of other characters or animals, but it would probably just work to loop the same sound that changes based on what terrain the sprite is over, and changes volume based on distance from center. Also, the loop could run concurrent with the movement animation loop.
boshaw wrote:
teleport
Menugrid.java triggers audio if you use teleport pagina
summoned
not sure how you might approach this one without it being spammed when it shouldn;t
log on
triggered after MapView.java widget is created
wake up
same as above + checks to see if your character is located ontop of a bed-type object
I meant these mainly to indicate what other characters were doing. These might be difficult to trigger. You could use the sudden appearance of a new character, but how do you differentiate between summoned characters and characters logging on or entering from a house?
boshaw wrote:
kin request denied
mmm could be triggered after the "Waiting..." window disappears and no new kin after X seconds
A window similar to the server messages pops up when kin requests are accepted or denied. This makes a sound redundant, but it could always be an option.

boshaw wrote:
wild death
triggered on death sprite for an animal after it was just in your Fightview Avatars
Or just any death sprite from an animal (would that be too unspecific? It could be triggered by the death cloud animation in that case).
boshaw wrote:
animal movement
triggered when the animal Gob moves its position: see player movement
Again, I was thinking that the trigger for this one would be the movement animation.

My main goals for doing a sound mod would be to make the current sounds less annoying, to provide the player with some information about what is happening nearby, and to make the environment more immersive. (I'll add this to the OP)
Cheerleader
User avatar
boshaw
Posts: 1612
Joined: Tue Jun 01, 2010 10:22 pm

Re: Client Sound Mods(?)

Post by boshaw »

Onionfighter wrote:I was hoping this would make a noise for anyone who happened to be nearby (similar to being able to hear stuff being dropped in water). Linking the trigger to the appearance of an item sprite might make it hard to distinguish between something that was dropped and something that appeared as you changed areas or moved around.
Yeah that would be a problem that you'd have to address if you wanted to make it like that. I don't have any good ideas on that yet.
Onionfighter wrote:I was thinking more along the lines of being able to hear other characters. I guess it would be more complicated to determine the speed of other characters or animals, but it would probably just work to loop the same sound that changes based on what terrain the sprite is over, and changes volume based on distance from center. Also, the loop could run concurrent with the movement animation loop.
If you add in other characters to this then before any audio clip is played for that gob you'd have to calculate its velocity (simple abs([(after_pos - before_pos) / time_between_frame]) ) and then map that velocity to a speed (round them for any measurement errors due to lag) and then you could play the audio clip for that player.
This would make you introduce a new variable to the Moving.java class i guess to keep track of previous position and current velocity. Very possible though.
this can be applied to animals as well that are moving since any Gob that moves will have a Moving.java Attribute when doing it.
Onionfighter wrote:I meant these mainly to indicate what other characters were doing. These might be difficult to trigger. You could use the sudden appearance of a new character, but how do you differentiate between summoned characters and characters logging on or entering from a house?
yeah in that case it's impossible imo to determine if they did that action or just walked into your draw range.
Onionfighter wrote:A window similar to the server messages pops up when kin requests are accepted or denied. This makes a sound redundant, but it could always be an option.
oh, shows how long since i've kinned anyone in game lol. that would make this easy then to do if wanted.
Onionfighter wrote: Or just any death sprite from an animal (would that be too unspecific? It could be triggered by the death cloud animation in that case).
death cloud animation sounds easier to do than checking for certain animal res's


Also fun magically stuff below:

Here's me taking a look at Audio.java and basically writing out everything in a simple mannner for someone to understand:
Note: read the `//' comments for my thought process on it as we go through

refer to : https://github.com/boshaw/haven-client/ ... Audio.java
First you should note: volume is a `public static double'
what does this mean?
--> There's only 1 copy of the volume variable which controls the volume for all clips. Also volume appears to go from 0.0 -> 1.0 (multiple by 100 to get %)

now the audio clips are invoked via `play' method as seen in Session code so when looking further into that


Code A

Code: Select all

public static void play(final Indir<Resource> clip) {
	//So we make a new anonymous class that extends Runnable and overrides method run
	//and we call queue with this new object created from above
	queue(new Runnable() {
		public void run() {
			//Gets the resource file
			Resource r = clip.get();
			if(r == null) // If it doesn't exist YET add it back to the queue to be looked at later
				queue.add(this); // this allows for server to send you the Resource file since you don't have it yet
			else//if resource has a clip then it plays it
				playres(r);
		}
	});
}
which shoots this off to `playres' method
Code B

Code: Select all

private static void playres(Resource res) {
	//Find all audio clips within the res  : AUDIO LAYER of res
	Collection<Resource.Audio> clips = res.layers(Resource.audio);
	int s = (int)(Math.random() * clips.size()); //pick a random clip number from [0->clip.size()]
	Resource.Audio clip = null; 
	//pick the clip to use based on s
	for(Resource.Audio cp : clips) { 
		clip = cp;
		if(--s < 0)
			break;
	}
	play(clip.clip); //play it
}
So lets take a look at what the Audio layer looks like first:
Refer to : https://github.com/boshaw/haven-client/ ... ource.java Resource.java
Lines 899 - 911
Code C

Code: Select all

public class Audio extends Layer {
	//byte array to hold all data from ogg formated clip
	transient public byte[] clip;
	
	public Audio(byte[] buf) {
		//This just reads in the clip based on the raw byte buffer from the *.res and then sends it through a decoder
		try {
			clip = Utils.readall(new VorbisDecoder(new ByteArrayInputStream(buf)));
		} catch(IOException e) {
			throw(new LoadException(e, Resource.this));
		}
	}

	public void init() {}
}
So the next method we want to look at is `play' but this time we want the one that takes a byte[] rather than Indir<Res>

Code D

Code: Select all

public static void play(byte[] clip) {
	play(clip, 1.0, 1.0);
}
//which then goes to:
public static void play(byte[] clip, double vol, double sp) {
    play(new DataClip(new java.io.ByteArrayInputStream(clip), vol, sp));
}
Originally i thought dataclips weren't used but i was wrong, they are and they have volumes and `sp' for themselves as well, but the default volume and sp is 1.0 (100%) and everything defaults with the current setup in Session.java

Now it goes even further to yet another `play' method:

Code E

Code: Select all

//note: DataClip implements CS
public static void play(CS clip) {
  synchronized(ncl) {
    ncl.add(clip); //add the clip to some collection
  }
  ckpl();
}

`ckpl' method simply starts the player if it's not already started or if audio is not enabled it clears the `ncl' collection list

Ok so now this is the end of the line for the ADDING process of a new audio clip into the audio class, now we need to look at the `Player' which is an inner Audio class class
to be exact: the `run' method
Code F

Code: Select all

public void run() {
	SourceDataLine line = null;
	try {
		try {
			line = (SourceDataLine)AudioSystem.getLine(new DataLine.Info(SourceDataLine.class, fmt));
			line.open(fmt, bufsize);
			line.start();
		} catch(Exception e) {
			e.printStackTrace();
			return;
		}
		byte[] buf = new byte[1024];
		while(true) { //This is where the action happens, the infinite loop of the player
			if(Thread.interrupted()) //If game is exiting: method which exits the thread that it's in
				throw(new InterruptedException());
			synchronized(queuemon) {
				//shallow copy the queue that we added to earlier
				Collection<Runnable> queue = Audio.queue;
				//basically erase all data in it so it doesn't loop through again unwanted
				Audio.queue = new LinkedList<Runnable>();
				for(Runnable r : queue) //for everything in that queue
					r.run(); //call its `run' method refer to `Code A' to see what its `run' method looks like
			}
			//Now if you remember from the the whole bunch of method calls from the r.run() found in `Code A' -> `Code E' 
			// the final product is added to the `ncl' collection list
			synchronized(ncl) {
				//So for everything in ncl
				for(CS cs : ncl) 
					//we add it to the clips collection
					clips.add(cs);
				//then we clear ncl for later use
				ncl.clear();
			}
			fillbuf(buf, 0, 1024); //refer to `Code G' below
			//below this wonderful for loop makes the audio actually play through some magical AudioSystem that I'm not going to go into
			for(int off = 0; off < buf.length; off += line.write(buf, off, buf.length - off));
		} //End action
	} catch(InterruptedException e) {
	} finally {
		synchronized(Audio.class) {
		player = null;
		}
		if(line != null)
		line.close();
	}
}
Code G

Code: Select all

//First of all:
//buf is a byte array of size 1024
//off is always 0
//len is always 1024; thus we cover the entire buffer
//nch is always 2 i think, not to sure about that
private void fillbuf(byte[] buf, int off, int len) {
	//make 2 double arrays of length nch
	double[] val = new double[nch];
	double[] sm = new double[nch];
	while(len > 0) { 
		//init val double array to 0.0 in all slots
		for(int i = 0; i < nch; i++)
			val[i] = 0;
		//For every audio clip we have in clips collection
		for(Iterator<CS> i = clips.iterator(); i.hasNext();) {
			CS cs = i.next(); //get the audio clip
			//the `get' method actually refers to `DataClip' class: method: `get'
			//it's a very fun method that does magically things that idk about
			//But what i do know is that it fills the double array sm with values needed from their audio clips
			//Note: within this `get' method the audio clips personal volume DOES come into play
			if(!cs.get(sm)) {
				i.remove();
				continue;
			}
			//those values filled by the audio clip then are added to the val double array slots
			for(int ch = 0; ch < nch; ch++)
				val[ch] += sm[ch];
		}
		//then this goes through and changes the values in buf due to values in val double array
		//which is also where volume comes into play:
		for(int i = 0; i < nch; i++) {
			//notice the volume variable that we noted above as static blah blah 1.0
			//Note: volume is really just the `master' volume whereas the volume find in each
			//DataClip class is that sub clips volume so Audio does support different volumes!!!
			int iv = (int)(val[i] * volume * 32767.0);
			if(iv < 0) {
				if(iv < -32768)
					iv = -32768;
				iv += 65536;
			} else {
				if(iv > 32767)
				iv = 32767;
			}
			buf[off++] = (byte)(iv & 0xff);
			buf[off++] = (byte)((iv & 0xff00) >> 8);
			len -= 2;
		}
	}
}



Conclusions:
  • different volumes per clip appear to be possible if you call the right `play' methods
    loftars default audio use [in Session.java] does not appear to bother with the custom volumes per clip
    adding audio to the game through triggers is very possible with slight edits of the Audio class and then the code for each trigger of course.

Return to “The Wizards' Tower”