XeNTaX Forum Index
Forum MultiEx Commander Tools Tools Home
It is currently Mon Oct 23, 2017 5:35 am

All times are UTC + 1 hour


Forum rules


Please click here to view the forum rules



Post new topic Reply to topic  [ 143 posts ]  Go to page Previous  1 ... 6, 7, 8, 9, 10
Author Message
 Post subject: Re: Frostbite 2 sound extraction research
PostPosted: Wed Nov 16, 2016 1:16 am 
Offline
veteran
User avatar

Joined: Fri Feb 18, 2011 4:58 pm
Posts: 126
Has thanked: 15 times
Have thanks: 15 times














You can make the ads go away by registering

Yeah, daemon1 and Frankelstner did a really really great job.

Thanks again guys for your work and patience. Useless post for most of readers, maybe, but kind of important for those who did help us (I guess).

FatalBulletHit: are you talking about extracting BF1 sounds? Because the oldest tools didn't work.

_________________
Vosvoy


Top
 Profile  
 
 Post subject: Re: Frostbite 2 sound extraction research
PostPosted: Wed Dec 14, 2016 10:30 am 
Offline
ultra-n00b
User avatar

Joined: Sun Nov 06, 2016 7:29 pm
Posts: 4
Has thanked: 4 times
Have thanks: 0 time
Vosvoy wrote:
FatalBulletHit: are you talking about extracting BF1 sounds? Because the oldest tools didn't work.

Nope, sry:
FatalBulletHit wrote:
However, if sb after me wants to have all the bf3 sounds and doesn't know how to...

:P


Top
 Profile  
 
 Post subject: Re: Frostbite 2 sound extraction research
PostPosted: Sat Jun 17, 2017 10:30 am 
Offline
ultra-n00b

Joined: Thu Nov 12, 2015 1:28 pm
Posts: 4
Has thanked: 1 time
Have thanks: 1 time
Hi,
I am interested in grabbing handheld weapons sounds from bf3. I extracted everything and I noticed that gunshot ebx files are significantly bigger and different than reloads ebx files. They also doesn't convert to wave using fb2audio.py. The script reads them, but doesnt convert to wave just as it does with reloads ebx. Does anyone know how to convert gunshots ebxes ?


Top
 Profile  
 
 Post subject: Re: Frostbite 2 sound extraction research
PostPosted: Wed Jun 21, 2017 9:33 pm 
Offline
ultra-n00b
User avatar

Joined: Sun Nov 06, 2016 7:29 pm
Posts: 4
Has thanked: 4 times
Have thanks: 0 time
rpopulik wrote:
Does anyone know how to convert gunshots ebxes ?


I don't, but as far as I know, every single gun shot is made with the sounds in the "shared_content" folder and not with the files you are talking about. No guarantee, tho, and I don't have a clue why there are additional bigger files in each of these sub folders.
However, if you are trying to get a clean gun shot you will get the job done quite well with the sounds in "shared_content". It also gives you all the sounds you need for an immersive gun shot, e.g. a M82 shot in a forest with deploying the bipod, handling the safety, a case dropping on dirt, reflection of the shot, bolt action, etc.

Hope I was able to help, feel free to report back if you have more questions! :)


Top
 Profile  
 
 Post subject: Re: Frostbite 2 sound extraction research
PostPosted: Mon Jun 26, 2017 8:59 am 
Offline
veteran
User avatar

Joined: Fri Feb 18, 2011 4:58 pm
Posts: 126
Has thanked: 15 times
Have thanks: 15 times
FatalBulletHit wrote:
rpopulik wrote:
Does anyone know how to convert gunshots ebxes ?


I don't, but as far as I know, every single gun shot is made with the sounds in the "shared_content" folder and not with the files you are talking about. No guarantee, tho, and I don't have a clue why there are additional bigger files in each of these sub folders.
However, if you are trying to get a clean gun shot you will get the job done quite well with the sounds in "shared_content". It also gives you all the sounds you need for an immersive gun shot, e.g. a M82 shot in a forest with deploying the bipod, handling the safety, a case dropping on dirt, reflection of the shot, bolt action, etc.

Hope I was able to help, feel free to report back if you have more questions! :)


You can convert those .ebx files to .txt files via a python script made by Frankelstner. Those files doesn't contain any sounds but informations about them.

Imagine that the wave samples are culinary ingredients, the ebx files are the recipes and the engine is the cook.

For example, I'm gonna make a REX MP412 gunfire sound. Thanks to the ebx file, I know what I need:

    - " CoreBassClose_OneShot_DoublePunch_Wave " for the punch/power (Shared_Content\CoreBassClose_OneShot\...).
    - " HiFi_Revolver_Wave " for the mechanical effect (Shared_Content\HiFi_OneShot\...) and put the speed of this sample at -60 in Audacity.
    - " Noise_Close_Rifle_Wave " to make the main stereo layer (Shared_Content\Noise_Layers\...).

Mix the whole thing with an additional impulse response sound of your choice (Shared_Content\Reflections\... or Shared_Content\Noise_Layers\...) and "Voilà, bon appétit".

It worked for BF3 and BF4 but I don't know if it works for BF1.

If you don't have it already, try this script:

Code:
#Requires Python 2.7
import string
import sys
from binascii import hexlify
from struct import unpack
import os
from cStringIO import StringIO
import cProfile
import cPickle

#adjust input and output folders here
inputFolder=r"H:\BF3SNDS\DUMP\bundles\ebx\sound"
outputFolder=r"H:\BF3SNDS\EBX"
guidTableName="guidTable bf" #name of the guid table file

EXTENSION=".txt"
SEP="    "

#the script can use the actual filenames in the explorer for the guid table (fast)
#or it can parse almost the entire file to retrieve the filename (slow, but necessary when the explorer names are just hashes)
#in case #2, create a separate guidTable file, in case#1 do not create that file.
#True/False
useExplorerNames=True


#ignore all instances and fields with these names when converting to text:
IGNOREINSTANCES=[]
IGNOREFIELDS=[]
##IGNOREINSTANCES=["ShaderAdjustmentData","SocketData","WeaponSkinnedSocketObjectData","WeaponRegularSocketObjectData"]
##IGNOREFIELDS=["Mesh3pTransforms","Mesh3pRigidMeshSocketObjectTransforms"]


#run createGuidTable or dumpText, or both (preferably in the right order)
#When using explorer names, do not change anything below.
#When not using explorer names you might want to make the guid table first, then restart the script to dump text only,
#though it should work fine without change too.
def main():
    createGuidTable()
    dumpText()




##############################################################
##############################################################
if useExplorerNames:
    def createGuidTable(): #guid vs filename
        for dir0, dirs, ff in os.walk(inputFolder):
            for fname in ff:
                path=os.path.join(dir0,fname)
                f=open(path,"rb")
                if f.read(4)!="\xCE\xD1\xB2\x0F":
                    f.close()
                    continue
                #grab the file guid directly, absolute offset 48 bytes
                f.seek(48)
                fileguid=f.read(16)
                f.close()
                filename=path[len(inputFolder):-4].replace("\\","/")
                guidTable[fileguid]=filename
else:
    def createGuidTable():
        for dir0, dirs, ff in os.walk(inputFolder):
            for fname in ff:
                f=open(dir0+"\\"+fname,"rb")
                if f.read(4)!="\xCE\xD1\xB2\x0F":
                    f.close()
                    continue
                dbx=Dbx(f)
                guidTable[dbx.fileGUID]=dbx.trueFilename
        f5=open(guidTableName,"wb") #write the table
        cPickle.dump(guidTable,f5)
        f5.close()

def dumpText():
    for dir0, dirs, ff in os.walk(inputFolder):
        for fname in ff:
            f=open(dir0+"\\"+fname,"rb")
            if f.read(4)!="\xCE\xD1\xB2\x0F":
                f.close()
                continue
            dbx=Dbx(f)
            dbx.dump(outputFolder)
           
try:
    from ctypes import *
    floatlib = cdll.LoadLibrary("floattostring")
    def formatfloat(num):
        bufType = c_char * 100
        buf = bufType()
        bufpointer = pointer(buf)
        floatlib.convertNum(c_double(num), bufpointer, 100)
        rawstring=(buf.raw)[:buf.raw.find("\x00")]
        if rawstring[:2]=="-.": return "-0."+rawstring[2:]
        elif rawstring[0]==".": return "0."+rawstring[1:]
        elif "e" not in rawstring and "." not in rawstring: return rawstring+".0"
        return rawstring
except:
    def formatfloat(num):
        return str(num)
def hasher(keyword): #32bit FNV-1 hash with FNV_offset_basis = 5381 and FNV_prime = 33
    hash = 5381
    for byte in keyword:
        hash = (hash*33) ^ ord(byte)
    return hash & 0xffffffff # use & because Python promotes the num instead of intended overflow
class Header:
    def __init__(self,varList): ##all 4byte unsigned integers
        self.absStringOffset     = varList[0]  ## absolute offset for string section start
        self.lenStringToEOF      = varList[1]  ## length from string section start to EOF
        self.numGUID             = varList[2]  ## number of external GUIDs
        self.null                = varList[3]  ## 00000000
        self.numInstanceRepeater = varList[4]
        self.numComplex          = varList[5]  ## number of complex entries
        self.numField            = varList[6]  ## number of field entries
        self.lenName             = varList[7]  ## length of name section including padding
        self.lenString           = varList[8]  ## length of string section including padding
        self.numArrayRepeater    = varList[9]
        self.lenPayload          = varList[10] ## length of normal payload section; the start of the array payload section is absStringOffset+lenString+lenPayload
class FieldDescriptor:
    def __init__(self,varList,keywordDict):
        self.name            = keywordDict[varList[0]]
        self.type            = varList[1]
        self.ref             = varList[2] #the field may contain another complex
        self.offset          = varList[3] #offset in payload section; relative to the complex containing it
        self.secondaryOffset = varList[4]
class ComplexDescriptor:
    def __init__(self,varList,keywordDict):
        self.name            = keywordDict[varList[0]]
        self.fieldStartIndex = varList[1] #the index of the first field belonging to the complex
        self.numField        = varList[2] #the total number of fields belonging to the complex
        self.alignment       = varList[3]
        self.type            = varList[4]
        self.size            = varList[5] #total length of the complex in the payload section
        self.secondarySize   = varList[6] #seems deprecated
class InstanceRepeater:
    def __init__(self,varList):
        self.null            = varList[0] #called "internalCount", seems to be always null
        self.repetitions     = varList[1] #number of instance repetitions
        self.complexIndex    = varList[2] #index of complex used as the instance
class arrayRepeater:
    def __init__(self,varList):
        self.offset          = varList[0] #offset in array payload section
        self.repetitions     = varList[1] #number of array repetitions
        self.complexIndex    = varList[2] #not necessary for extraction
class Complex:
    def __init__(self,desc):
        self.desc=desc
class Field:
    def __init__(self,desc):
        self.desc=desc

numDict={0x0035:("I",4),0xc10d:("I",4),0xc14d:("d",8),0xc0ad:("?",1),0xc0fd:("i",4),0xc0bd:("B",1),0xc0ed:("h",2), 0xc0dd:("H",2), 0xc13d:("f",4)}

class Dbx:
    def __init__(self, f):
        #metadata
        self.trueFilename=""
        self.header=Header(unpack("11I",f.read(44)))
        self.arraySectionstart=self.header.absStringOffset+self.header.lenString+self.header.lenPayload
        self.fileGUID, self.primaryInstanceGUID = f.read(16), f.read(16)   
        self.externalGUIDs=[(f.read(16),f.read(16)) for i in xrange(self.header.numGUID)]
        self.keywords=str.split(f.read(self.header.lenName),"\x00")
        self.keywordDict=dict((hasher(keyword),keyword) for keyword in self.keywords)
        self.fieldDescriptors=[FieldDescriptor(unpack("IHHII",f.read(16)), self.keywordDict) for i in xrange(self.header.numField)]
        self.complexDescriptors=[ComplexDescriptor(unpack("IIBBHHH",f.read(16)), self.keywordDict) for i in xrange(self.header.numComplex)]
        self.instanceRepeaters=[InstanceRepeater(unpack("3I",f.read(12))) for i in xrange(self.header.numInstanceRepeater)]
        while f.tell()%16!=0: f.seek(1,1) #padding
        self.arrayRepeaters=[arrayRepeater(unpack("3I",f.read(12))) for i in xrange(self.header.numArrayRepeater)]

        #payload
        f.seek(self.header.absStringOffset+self.header.lenString)
        self.internalGUIDs=[]
        self.instances=[] # (guid, complex)
        for instanceRepeater in self.instanceRepeaters:
            for repetition in xrange(instanceRepeater.repetitions):
                instanceGUID=f.read(16)
                self.internalGUIDs.append(instanceGUID)
                if instanceGUID==self.primaryInstanceGUID: self.isPrimaryInstance=True
                else: self.isPrimaryInstance=False
               
                self.instances.append( (instanceGUID,self.readComplex(instanceRepeater.complexIndex,f)) )
        f.close()
       
       

    def readComplex(self, complexIndex,f):
        complexDesc=self.complexDescriptors[complexIndex]
        cmplx=Complex(complexDesc)
       
        startPos=f.tell()                 
        cmplx.fields=[]
        for fieldIndex in xrange(complexDesc.fieldStartIndex,complexDesc.fieldStartIndex+complexDesc.numField):
            f.seek(startPos+self.fieldDescriptors[fieldIndex].offset)
            cmplx.fields.append(self.readField(fieldIndex,f))
       
        f.seek(startPos+complexDesc.size)
        return cmplx


    def readField(self,fieldIndex,f):
        fieldDesc = self.fieldDescriptors[fieldIndex]
        field=Field(fieldDesc)
       
        if fieldDesc.type in (0x0029, 0xd029,0x0000):
            field.value=self.readComplex(fieldDesc.ref,f)
        elif fieldDesc.type==0x0041:
            arrayRepeater=self.arrayRepeaters[unpack("I",f.read(4))[0]]
            arrayComplexDesc=self.complexDescriptors[fieldDesc.ref]

##            if arrayRepeater.repetitions==0: field.value = "*nullArray*"
            f.seek(self.arraySectionstart+arrayRepeater.offset)
            arrayComplex=Complex(arrayComplexDesc)
            arrayComplex.fields=[self.readField(arrayComplexDesc.fieldStartIndex,f) for repetition in xrange(arrayRepeater.repetitions)]
            field.value=arrayComplex
           
        elif fieldDesc.type in (0x407d, 0x409d):
            startPos=f.tell()
            f.seek(self.header.absStringOffset+unpack("I",f.read(4))[0])
            string=""
            while 1:
                a=f.read(1)
                if a=="\x00": break
                else: string+=a
            f.seek(startPos+4)
           
            if len(string)==0: field.value="*nullString*" #actually the string is ""
            else: field.value=string
           
            if self.isPrimaryInstance and self.trueFilename=="" and fieldDesc.name=="Name": self.trueFilename=string
           
                   
        elif fieldDesc.type==0x0089: #incomplete implementation, only gives back the selected string
            compareValue=unpack("I",f.read(4))[0]
            enumComplex=self.complexDescriptors[fieldDesc.ref]

            if enumComplex.numField==0:
                field.value="*nullEnum*"
            for fieldIndex in xrange(enumComplex.fieldStartIndex,enumComplex.fieldStartIndex+enumComplex.numField):
                if self.fieldDescriptors[fieldIndex].offset==compareValue:
                    field.value=self.fieldDescriptors[fieldIndex].name
                    break
        elif fieldDesc.type==0xc15d:
            field.value=f.read(16)
        else:
            (typ,length)=numDict[fieldDesc.type]
            num=unpack(typ,f.read(length))[0]
            field.value=num
       
        return field
       

    def dump(self,outputFolder):
        dirName=os.path.dirname(outputFolder+self.trueFilename)
        if not os.path.isdir(dirName): os.makedirs(dirName)
        if not self.trueFilename: self.trueFilename=hexlify(self.fileGUID)
        f2=open(outputFolder+self.trueFilename+EXTENSION,"wb")
        print self.trueFilename
       
        for (guid,instance) in self.instances:
            if instance.desc.name not in IGNOREINSTANCES: #############
                if guid==self.primaryInstanceGUID: f2.write(instance.desc.name+" "+hexlify(guid)+ " #primary instance\r\n")
                else: f2.write(instance.desc.name+" "+hexlify(guid)+ "\r\n")
                self.recurse(instance.fields,f2,0)
        f2.close()

    def recurse(self, fields, f2, lvl): #over fields
        lvl+=1
        for field in fields:
            if field.desc.type in (0xc14d, 0xc0fd, 0xc10d, 0xc0ed, 0xc0dd, 0xc0bd, 0xc0ad, 0x407d, 0x409d, 0x0089):
                f2.write(lvl*SEP+field.desc.name+" "+str(field.value)+"\r\n")
            elif field.desc.type == 0xc13d:
                f2.write(lvl*SEP+field.desc.name+" "+formatfloat(field.value)+"\r\n")
            elif field.desc.type == 0xc15d:
                f2.write(lvl*SEP+field.desc.name+" "+hexlify(field.value).upper()+"\r\n") #upper case=> chunk guid
            elif field.desc.type == 0x0035:
                towrite=""
                if field.value>>31:
                    extguid=self.externalGUIDs[field.value&0x7fffffff]
                    try: towrite=guidTable[extguid[0]]+"/"+hexlify(extguid[1])
                    except: towrite=hexlify(extguid[0])+"/"+hexlify(extguid[1])
                elif field.value==0: towrite="*nullGuid*"
                else: towrite=hexlify(self.internalGUIDs[field.value-1])
                f2.write(lvl*SEP+field.desc.name+" "+towrite+"\r\n")
            elif field.desc.type==0x0041 and len(field.value.fields)==0:
                f2.write(lvl*SEP+field.desc.name+" "+"*nullArray*"+"\r\n")
            else:
                if field.desc.name not in IGNOREFIELDS: #############
                    f2.write(lvl*SEP+field.desc.name+"::"+field.value.desc.name+"\r\n")
                    self.recurse(field.value.fields,f2,lvl)


if outputFolder[-1] not in ("/","\\"): outputFolder+="/"
if inputFolder[-1] not in ("/","\\"): inputFolder+="/"


#if there's a guid table already, use it
try:
    f5=open(guidTableName,"rb")
    guidTable=cPickle.load(f5)
    f5.close()
except:
    guidTable=dict()


main()


(I'm not at home now so I just copy/pasted this script from an ancient post on this thread so tell me if this script doesn't work)

Cordialy.

_________________
Vosvoy


Top
 Profile  
 
 Post subject: Re: Frostbite 2 sound extraction research
PostPosted: Tue Jul 04, 2017 1:38 pm 
Offline
ultra-n00b
User avatar

Joined: Sun Nov 06, 2016 7:29 pm
Posts: 4
Has thanked: 4 times
Have thanks: 0 time
Vosvoy wrote:
(I'm not at home now so I just copy/pasted this script from an ancient post on this thread so tell me if this script doesn't work)

Works totally fine, had the exact same script already on my HDD but apparently never used it.

Just one question:
"Weapon_SMG_UMP45_Silenced_02.txt" has 5498 lines of code... I understand the 56 paths, but what are the other 5442 lines about? :?
I mean, what is important and how do you know stuff like this:
Vosvoy wrote:
[...] put the speed of this sample at -60 in Audacity.

? :oops:


Top
 Profile  
 
 Post subject: Re: Frostbite 2 sound extraction research
PostPosted: Thu Jul 06, 2017 3:29 am 
Offline
mega-veteran
mega-veteran

Joined: Sun Aug 22, 2010 10:14 pm
Posts: 160
Has thanked: 34 times
Have thanks: 9 times
Any change someone could look at the audio stuff from battlefront 2 not sure if it's the sounds that's changed or the ebx format has changed but if anyone needs anything else like a chunk file to go along with that ebx file tell me the names...ect and i'll send it


Attachments:


You do not have the required permissions to view the files attached to this post. Register to gain access.



Top
 Profile  
 
 Post subject: Re: Frostbite 2 sound extraction research
PostPosted: Tue Jul 11, 2017 4:19 am 
Offline
veteran
User avatar

Joined: Fri Feb 18, 2011 4:58 pm
Posts: 126
Has thanked: 15 times
Have thanks: 15 times
FatalBulletHit wrote:
Vosvoy wrote:
Just one question:
"Weapon_SMG_UMP45_Silenced_02.txt" has 5498 lines of code... I understand the 56 paths, but what are the other 5442 lines about? :?
I mean, what is important and how do you know stuff like this:
Vosvoy wrote:
[...] put the speed of this sample at -60 in Audacity.



Here:

Wave ebx/sound/weapons/shared_content/hifi_oneshot/hifi_revolver_wave/8cac79083715480ad77264b7c25229c8
BasePitch 0.600000023842
Loop LtNone
ShuffleSegments False
Plugins::array*

I decided a long time ago to mess around with BF3 sounds and I found this conclusion:

+/- 0.10 Ebx BasePitch points = +/- 10 speed points in Audacity

E.g:
> BasePitch: 1.00 = default.
> BasePitch: 0.60 = -40 speed points in Audacity [0.60 (ebx base pitch) - 1.00 (default base pitch) = -0.40].
> BasePitch: 2.00 = +200 speed points in Audacity.
> BasePitch: 1.10 = +10 speed points in Audacity.
> BasePitch: 0.80 = -20 speed points in Audacity.
Etc...

I don't speak english so I don't even know how to explain all this thing, but I have one last advice for you.

Don't even try to understand the whole ebx mumbo jumbo. The most important things are "BasePitch" points and "Wave ebx/sound/..." directories. That's all.

EDIT: I corrected a few things.

_________________
Vosvoy


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 143 posts ]  Go to page Previous  1 ... 6, 7, 8, 9, 10

All times are UTC + 1 hour


Who is online

Users browsing this forum: No registered users and 1 guest


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group