Client Sound Mods(?)

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

Moderator: Phades

Re: Client Sound Mods(?)

Postby boshaw » Tue Dec 20, 2011 7:51 am

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
boshaw
 
Posts: 1590
Joined: Tue Jun 01, 2010 10:22 pm

Re: Client Sound Mods(?)

Postby Onionfighter » Tue Dec 20, 2011 8:22 am

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
Onionfighter
 
Posts: 2957
Joined: Sat May 30, 2009 8:45 am
Location: Mordor

Re: Client Sound Mods(?)

Postby boshaw » Tue Dec 20, 2011 9:08 am

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.
User avatar
boshaw
 
Posts: 1590
Joined: Tue Jun 01, 2010 10:22 pm

Previous

Return to The Wizards' Tower

Who is online

Users browsing this forum: Claude [Bot] and 1 guest