Looping a sound - how do I hack that?

Post » Tue Jan 04, 2011 2:10 pm

Hi all,

I created a 10 second sound that I'd like to loop. It's supposed to be a continuous sound (a grinder humming, churning, etc.) so it can't have a gap between loops. Even a split second sounds bad.

The number of loops will vary, so I tried a simple timer and PlaySound3D
			set TimePassed to ((GetCurrentTime - GameHourPrev) + 24*(GameDaysPassed - GameDaysPrev))*3600/TimeScale			if (TimePassed <= 0)				set TimePassed to GetSecondsPassed			endif			if (TimePassed <= 0) ;Ultra-paranoid check - I can't imagine why GSP would be 0, but just in case (really, a ' is a comment here...)				set TimePassed to 0			endif			set GameHourPrev to GetCurrentTime			set GameDaysPrev to GameDaysPassed...				set NoiseTimer to (NoiseTimer + TimePassed)...				if (NoiseTimer >= 1) ;Has to be 1, not the actual length of the sound or even half or even 2 - all end up with a gap in the loop					set NoiseTimer to 0					PlaySound3D cobGrinderSND				endif

Whole script
Spoiler
;--DO NOT MODIFY THIS SCRIPT!!!
; DOING SO WILL CAUSE CTDS AND STRANGE BEHAVIOR FOR USERS.
; If you need this script to do more, have a suggestion, or an alteration:
; contact the Cobl team at: http://www.uesp.net/wiki/Tes4Mod:Cobl/Modding/Board

;--VERSION HISTORY
; v1.73 Grinder overhaul
; v1.30 Fix silver to gold transmutation.
; v1.?? Rewrite so that grinding processes one item at a time.
; v1.06 First version.

scn cobGrinderOS
;--Script for grinder container
short OpenStage ;Must wait for menu screen to open, so 1 for opening and 2 for 'menu has opened'
float PauseForTokens
float SecondsUntilDone
float TimePassed
float NoiseTimer
float gameHourPrev
short gameDaysPrev
;--Obsolete
short action
float pause


begin onActivate
if PauseForTokens
if (SecondsUntilDone < 0) ;Tokens haven't run yet, or there wasn't any items to grind
set PauseForTokens to 0
set SecondsUntilDone to 0
set OpenStage to 1
Activate
else ;Tokens have run and there are items to grind... so the player is too late
Message "Ouch! That's hot! (%.0f seconds until done)" SecondsUntilDone
endif
elseif SecondsUntilDone
if (SecondsUntilDone > 1)
Message "Ouch! That's hot! (%.0f seconds until done)" SecondsUntilDone
;cast a 0 pt Fire spell at player
else ;if (SecondsUntilDone <= 1)
Message "It's cooling down... just another second..."
endif
elseif Pause ;Update grinder (pause only set for old versions)
;Older versions of Grinder would grind item by item
;That is, an "if (cont.GetItemCount GrindableItem) -> Grind" block for each item
;If the item was in the grinder then it would switch it out for the grinded item and add up the time for to Pause
;So the container could contain both grinded and ungrinded items
;Pause would only be long enough for one item stack, and at 1 second per quantity it would take 60 Flawless Diamonds a minute to grind
set OpenStage to 2 ;Run as if it's been opened, closed, items left to check
;set SecondsUntilDone to Pause ;Not necessary - not likely to be a very high number
set Pause to 0
Message "It seems to be stuck... no, wait, here it goes..."
else
Activate
set OpenStage to 1
endif
end


begin menuMode 1008
if OpenStage
set OpenStage to 2
endif
end


begin gameMode
if (OpenStage == 2)
;--Reset variables
set OpenStage to 0
set NoiseTimer to 0
set TimePassed to 0
set GameHourPrev to GetCurrentTime
set GameDaysPrev to GameDaysPassed
set PauseForTokens to 0.5
set SecondsUntilDone to 0
;--Grind FAR global variables
set cobGrindFAR.rCont to GetSelf
set cobGrindFAR.MoreSeconds to 0
;--Run the tokens by moving them here
if ((cobGrindListReaderCont.GetInSameCell player) == 0)
cobGrindListReaderCont.MoveTo player
endif
cobGrindListReaderCont.RemoveAllItems
cobGrindListCont.RemoveAllItems cobGrindListReaderCont
endif

if SecondsUntilDone || PauseForTokens
if (GetInSameCell player) ;--Keeps the activator from running when player leaves the cell (setting variables makes the script run once more, so if a variable is set every time the script keeps running)
;--Calculate the amount of time that has passed
set TimePassed to ((GetCurrentTime - GameHourPrev) + 24*(GameDaysPassed - GameDaysPrev))*3600/TimeScale
if (TimePassed <= 0)
set TimePassed to GetSecondsPassed
endif
if (TimePassed <= 0) ;Ultra-paranoid check - I can't imagine why GSP would be 0, but just in case
set TimePassed to 0
endif
set GameHourPrev to GetCurrentTime
set GameDaysPrev to GameDaysPassed
;--Figure out new SecondsUntilDone
if cobGrindFAR.MoreSeconds
set SecondsUntilDone to (SecondsUntilDone + cobGrindFAR.MoreSeconds)
set cobGrindFAR.MoreSeconds to 0
endif
set SecondsUntilDone to (SecondsUntilDone - TimePassed)
set NoiseTimer to (NoiseTimer + TimePassed)
;--Keep running or stop
if PauseForTokens
set PauseForTokens to (PauseForTokens - TimePassed)
if (PauseForTokens < 0)
set PauseForTokens to 0
if (SecondsUntilDone > 0)
PlayMagicShaderVisuals cobGrinderFX
PlaySound3D cobGrinderSND
endif
endif
elseif (SecondsUntilDone > 0)
if (NoiseTimer >= 1) ;Has to be 1, not the actual length of the sound or even half or even 2 - all end up with a gap in the loop
set NoiseTimer to 0
PlaySound3D cobGrinderSND
endif
else
set SecondsUntilDone to 0
;play some kind of winding-down sound
StopMagicShaderVisuals cobGrinderFX
endif
endif
endif
end

It seems that PlaySound3D is ignored while the sound is playing. So it will play for 10 seconds, then won't start again until the timer has counted down... so somewhere between 0-1 seconds. Even removing the timer and playing every frame still has a noticeable gap. (I'm seeing TimePassed of .160, even though my FPS is certainly higher than 5 FPS... so I'm not sure what's going on there... I though object scripts ran every frame, but :shrug: )

Any suggestions?

I read a hack on the wiki. Basically, you select the "Loop" sound flag for the sound. To turn it on and off you call PlaySound3D on another object to get the sound started and Disable it to stop it... and I guess use MoveTo to move it around and Enable to get it playing again... any one tried it?
User avatar
Donatus Uwasomba
 
Posts: 3361
Joined: Sun May 27, 2007 7:22 pm

Post » Tue Jan 04, 2011 8:59 pm

Set the sound object up to loop.
Then drop the sound object where you want it heard in the Render Window, and use a script to 'enable' it when you want to hear it, and 'disable' it when you don't.

There is no reason to use 'PlaySound3D' on a sound object in the render window. Looped sound objects will continually play when they are enabled, and stop playing when they are disabled.

Its not a 'hack', its how they are made to work.
User avatar
gemma king
 
Posts: 3523
Joined: Fri Feb 09, 2007 12:11 pm

Post » Tue Jan 04, 2011 6:52 pm

No, what I read on the wiki was to play the sound on a modelless object (say, an activator) and use MoveTo/Disable/Enable and most certainly was a hack.

What you're saying, though, is that I can plop a Sound Object itself into the World... that is freakin' awesome!

Now, can I move this puppy around with MoveTo? Also, will PlaySound still make it sound as if it's coming from a location. That is, if the player turns to the left will they only hear it out of their right speaker?
User avatar
Casey
 
Posts: 3376
Joined: Mon Nov 12, 2007 8:38 am

Post » Wed Jan 05, 2011 12:06 am

PlaySound is NOT locational. A sound object in the render window IS locational.

Instead of 'moving' the sound object, simply place one sound object everywhere possible you want it to play. Set them all to 'initially disabled'. Then enable the single sound object you want played at the location you want it played and disable it when your done at that location. Then enable a different sound object at a different location. Its really very simple.
User avatar
Sheeva
 
Posts: 3353
Joined: Sat Nov 11, 2006 2:46 am

Post » Wed Jan 05, 2011 3:45 am

It's a modder's resource, so I can't drop it in place.

However, MoveTo seems to work. And I don't even need to use PlaySound to get it going, just Enable and it plays... and it even stops exactly when I disable it! Thanks for the help.
User avatar
Shaylee Shaw
 
Posts: 3457
Joined: Wed Feb 21, 2007 8:55 pm

Post » Wed Jan 05, 2011 5:15 am

See that the sound issue is solved, but I just had to comment on your timer use, which seems overly complicated. Here is how I would make a timer that made something happen every 3 second (just as an example):

set NoiseTimer to NoiseTimer + GetSecondsPassedif NoiseTimer >= 3   set NoiseTimer to 0   endif


This is all you need for an exact timer - and if this script runs often enough, checking against the actual length of the sound will work without any gap - I guess the gap problem comes from your complicated, inaccurate timer. Not that you should change the code back to using PlaySound3D and a timer, but timers are often nice to have and they shouldn't be more complicated than this :)
User avatar
Jamie Moysey
 
Posts: 3452
Joined: Sun May 13, 2007 6:31 am

Post » Tue Jan 04, 2011 7:44 pm

...

That's what Wrye had. It's really to handle leaving the cell and coming back - in which GetSecondsPassed wouldn't work... at least, IIRC, GSP only returns the amount of time between the last frame, not the last call (it's been a while, so remind me if I'm wrong there). It's the Cobl grinder, which takes about a second per item to grind, so you can be looking at a few minutes. The player can leave while it's busy and come back later.
User avatar
Julie Serebrekoff
 
Posts: 3359
Joined: Sun Dec 24, 2006 4:41 am


Return to IV - Oblivion