# Author: Shao Zhang, Phil Saltzman, and Elan Ruskin
# Last Updated: 4/20/2005
#
# This tutorial shows how to load, play, and manipulate sounds
# and sound intervals in a panda project.

import direct.directbase.DirectStart
from pandac.PandaModules import *
from direct.interval.IntervalGlobal import *
from direct.gui.DirectGui import *
from direct.showbase.DirectObject import *
import sys

class World(DirectObject):
  def __init__(self):
  
    #Our standard title and instructions text
    self.title = OnscreenText(text="Panda3D: Tutorial - Musicbox(sounds)",
                              style=1, fg=(1,1,1,1), pos=(0.8,-0.95), scale = .07)
    self.escapeEventText = OnscreenText(text="ESC: Quit",
                                    style=1, fg=(1,1,1,1), pos=(-1.3, 0.95),
                                    align=TextNode.ALeft, scale = .05)
  
    #Set up the key input 
    self.accept('escape', sys.exit)

    #Fix the camera position
    base.disableMouse()

    #Loading sounds is done in a similar way to loading other things
    #Loading the main music box song
    self.musicBoxSound = base.loadMusic('music/musicbox.mp3')
    self.musicBoxSound.setVolume(.5)   #Volume is a percentage from 0 to 1
    self.musicBoxSound.setLoopCount(0) #0 means loop forever, 1 (default) means
                                       #play once. 2 or higher means play that
                                       #many times

    #Sound objects do not have a pause function, just play and stop. So we will
    #Use this variable to keep track of where the sound is at when it was stoped
    #to impliment pausing
    self.musicTime = 0

    #Loading the open/close effect
    #loadSFX and loadMusic are identical. They are often used for organization
    #(loadMusic is used for background music, loadSfx is used for other effects)
    self.lidSfx = base.loadSfx('music/openclose.mp3')
    #The open/close file has both effects in it. Fortunatly we can use intervals
    #to easily define parts of a sound file to play
    self.lidOpenSfx = SoundInterval(self.lidSfx, duration = 2, startTime = 0)
    self.lidCloseSfx = SoundInterval(self.lidSfx, startTime = 5)

    #For this tutorial, it seemed appropriate to have on screen controls. The
    #following code creates them
    #This is a label for a slider
    self.sliderText = OnscreenText("Volume",
                    style=1, fg=(1,1,1,1), pos=(0,0.8), scale = .07)
    #The slider itself. It calls self.setMusicBoxVolume when changed
    self.slider = DirectSlider(pos = Vec3(0,0,.7), value = .50,
                    command = self.setMusicBoxVolume)
    #A button that calls self.toggleMusicBox when pressed
    self.button = DirectButton(pos = Vec3(.7,0,.7), text = "Open Box",
                   scale = .1, pad = (.5, .5),
                   rolloverSound = None, clickSound = None,
                   command = self.toggleMusicBox)

    #A variable to represent the state of the simulation. It starts closed
    self.boxOpen = False

    #Here we load and set up the music box. It was modeled in a complex way, so
    #setting it up will be complicated
    self.musicBox=loader.loadModel('models/MusicBox')
    self.musicBox.setPos(0, 60, -10)
    self.musicBox.reparentTo(render)
    #Just like the scene graph contains hierarchies of nodes, so can
    #models. You can get the NodePath for the node using the find
    #function, and then you can animate the model by moving its parts
    #To see the hierarchy of a model, use, the ls function
    #self.musicBox.ls() prints out the entire hierarchy of the model

    #Finding pieces of the model
    self.Lid   = self.musicBox.find('**/lid')
    self.Panda = self.musicBox.find('**/turningthing')  

    #This model was made with the hinge in the wrong place
    #this is here so we have something to turn
    self.HingeNode = self.musicBox.find('**/box').attachNewNode('nHingeNode')
    self.HingeNode.setPos(.8659,6.5,5.4)
    #WRT - ie with respect to. Reparents the object without changing
    #its position, size, or orientation
    self.Lid.wrtReparentTo(self.HingeNode)
    self.HingeNode.setHpr(0,90,0)

    #This sets up an interval to play the close sound and actually close the box
    #at the same time.
    self.lidClose = Parallel(
      self.lidCloseSfx,
      LerpFunc(self.HingeNode.setP, duration = 2,
           fromData = 0, toData = 90,
           blendType ='easeInOut'))

    #Same thing for opening the box
    self.lidOpen = Parallel(
      self.lidOpenSfx,
      LerpFunc(self.HingeNode.setP, duration = 2,
           fromData = 90, toData = 0,
           blendType ='easeInOut'))

    #The interval for turning the panda
    self.PandaTurn=self.Panda.hprInterval( 7, Vec3(360,0,0) )
    #Do a quick loop and pause to set it as a looping interval so it can be
    #started with resume and loop properly
    self.PandaTurn.loop()
    self.PandaTurn.pause()

  def setMusicBoxVolume(self):
    #Simply reads the current value from the slider and sets it in the sound
    newVol = self.slider.guiItem.getValue()
    self.musicBoxSound.setVolume(newVol)

  def toggleMusicBox(self):
    if self.boxOpen:
      #close the box
      self.lidClose.start()               #Start the close box interval
      self.PandaTurn.pause()              #Pause the figurine turning
      #Save the current time of the music box song
      self.musicTime = self.musicBoxSound.getTime() 
      self.musicBoxSound.stop()           #Stop the music box song
      self.button['text'] = "Open Box"    #Prepare to change button label
    else:
      #open the box
      self.lidOpen.start()                #Start the open box interval
      self.PandaTurn.resume()             #Resume the figuring turning
      #Reset the time of the music box song so it starts where it left off
      self.musicBoxSound.setTime(self.musicTime)
      self.musicBoxSound.play()           #Play the music box song
      self.button['text'] = "Close Box"   #Prepare to change button label

    self.button.setText()                 #Actually change the button label
    self.boxOpen = not self.boxOpen       #Set our state to opposite what it was
                                          #(closed to open or open to closed)

# and we can run!
w = World()
run()
