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
; 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?