from sugar.activity import activity

from Csound import *
from CsoundServer import *
from CsoundEventSequencer import *

import random
import sys, os
import gtk

import gobject
import time

import os
from os.path import join, getsize



class CSRemixerActivity(activity.Activity):
    orchestra = """
nchnls = 2

gkVol1 init -6.0
gkVol1 chnexport "track1vol", 1, 0, i(gkVol1), -36.0, 12.0
gkVol2 init -6.0
gkVol2 chnexport "track2vol", 1, 0, i(gkVol2), -36.0, 12.0
gkVol3 init -6.0
gkVol3 chnexport "track3vol", 1, 0, i(gkVol3), -36.0, 12.0
gkVol4 init -6.0
gkVol4 chnexport "track4vol", 1, 0, i(gkVol4), -36.0, 12.0

instr 1 ; play table
    iDur    = p3
    iTable  = p4
    iSR     = p5
    
    iLen = ftlen(iTable)
    iRate = 1.0 / (iLen / iSR)

    aPhase  phasor iRate
    aPhase = aPhase * iLen

    aOut    tab     aPhase, iTable
    aOut = aOut * ampdbfs(gkVol1)
    
        outs    aOut, aOut
endin

instr 2 ; play table
    iDur    = p3
    iTable  = p4
    iSR     = p5
    
    iLen = ftlen(iTable)
    iRate = 1.0 / (iLen / iSR)

    aPhase  phasor iRate
    aPhase = aPhase * iLen

    aOut    tab     aPhase, iTable
    aOut = aOut * ampdbfs(gkVol2)
    
        outs    aOut, aOut
endin

instr 3 ; play table
    iDur    = p3
    iTable  = p4
    iSR     = p5
    
    iLen = ftlen(iTable)
    iRate = 1.0 / (iLen / iSR)

    aPhase  phasor iRate
    aPhase = aPhase * iLen

    aOut    tab     aPhase, iTable
    aOut = aOut * ampdbfs(gkVol3)
    
        outs    aOut, aOut
endin

instr 4 ; play table
    iDur    = p3
    iTable  = p4
    iSR     = p5
    
    iLen = ftlen(iTable)
    iRate = 1.0 / (iLen / iSR)

    aPhase  phasor iRate
    aPhase = aPhase * iLen

    aOut    tab     aPhase, iTable
    aOut = aOut * ampdbfs(gkVol4)
    
        outs    aOut, aOut
endin






instr 110 ; load instrument

kCounter init 0
    
    Sfname      strget	p4

    Spathback       sprintf	"%d/len", p5    
    Spathbackchan	sprintf	"%d/channels", p5    
    Spathbacksr     sprintf	"%d/sr", p5    
    
    ichannels filenchnls Sfname
    isr       filesr    Sfname
    
    iftno	ftgen	p5, 0, 0, 1, Sfname, 0, 0, 1

if kCounter >= 0 kgoto done    
            outvalue Spathback, ftlen(iftno)
            outvalue Spathbackchan, ichannels
            outvalue Spathbacksr, isr            
done:
   
endin

"""    

    def destroy(self, data=None):
        self.eventSequencer.shutdownServer()        
        activity.Activity.destroy(self)


    def __init__(self, handle):
        activity.Activity.__init__(self, handle)
        self.set_title('CSRemixer')
                
        # Creates the Toolbox. It contains the Activity Toolbar, which is the
        # bar that appears on every Sugar window and contains essential
        # functionalities, such as the 'Collaborate' and 'Close' buttons.
        toolbox = activity.ActivityToolbox(self)
        self.set_toolbox(toolbox)
        toolbox.show()
        
        self.currentTime = 0
        
        server = CsoundServer()
        fd = server.launch(sampleRate=22050)
        #if fd != None:
        #    gobject.io_add_watch(fd.fileno(), gobject.IO_IN, self.serverMessages, fd)

        print "setup"
        self.trackLoops = []
        self.eventSequencer = CsoundEventSequencer("Remixer", self.orchestra)
        self.eventSequencer.setTrackAddedCallback(self.trackAdded)
        
        localConnection = self.eventSequencer.addLocalConnection('localhost', 7000, sampleRate=22050)
        if localConnection != None:
            # we only want to get time notifications from our local server
            localConnection.registerTime(self.timeUpdate, 0.2) # 10x per sec
            self.audioFiles = AudioFiles(localConnection)
            
        print "setup2"
        
        for i in range(4):
            self.eventSequencer.requestNewTrack()
    
        self.eventSequencer.setTempo(130)
        
        print "widgets"
        self.layout = self.setupWidgets()        
        self.layout.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse("#C0C0C0"))        
        self.layout.show_all()

        print "post widgets"
        self.set_canvas(self.layout)
        
                


    def setupWidgets(self):
        layout = gtk.Layout()

        # transport
        self.playButton = gtk.Button("play")
        self.playButton.set_size_request(100, 40)
        self.playButton.connect("clicked", self.play, None)
        
        self.pauseButton = gtk.Button("pause")
        self.pauseButton.set_size_request(100, 40)
        self.pauseButton.connect("clicked", self.pause, None)
        
        self.stopButton = gtk.Button("stop")
        self.stopButton.set_size_request(100, 40)
        self.stopButton.connect("clicked", self.stop, None)

        self.clearButton = gtk.Button("clear")
        self.clearButton.set_size_request(100, 40)
        self.clearButton.connect("clicked", self.clear, None)


        self.time = gtk.Label("0.0")

        # header layout
        layout.put(self.playButton, 10, 10)
        layout.put(self.pauseButton, 110, 10)
        layout.put(self.stopButton, 210, 10)
        layout.put(self.clearButton, 310, 10)        
        layout.put(self.time, 510, 20)
                            
        return layout

    def serverMessages(self, source, condition, userData = None):
        #print "SERVER:", userData.readline(),
        return True

    
    def play(self, widget, data = None):
        self.eventSequencer.play()

    def stop(self, widget, data = None):
        self.eventSequencer.stop()

    def pause(self, widget, data = None):    
        self.eventSequencer.pause()

    def clear(self, widget, data = None):    
        for i in range(4):
            self.eventSequencer.clearTrack(i)

        
    def calculateNextStart(self):
        return ( int( int(self.currentTime) / 4) ) * 4 + 4                
    
    def cueAudioFileOnTrack(self, audioFile, track, start):
        if audioFile != None:
            dur = audioFile.duration() * self.tempoScale()
            #print "start=", start, "index=", index, "dur=", dur        
            track.requestNewEvent(start, dur, audioFile.fTableIndex(), [audioFile.sampleRate()])
            
    def playTrack(self, widget, track = None):
        index = track.index()
        
        if not widget.get_active():

            for t in self.trackLoops:
                if t.track().index() == index:

                    if t.isPlaying():
                        # should only play one at a time
                        widget.set_active(True)
                        return

            return
                

        for t in self.trackLoops:
            if t.track().index() == index:

                if t.isPlaying():
                    # should only play one at a time
                    widget.set_active(True)
                    return

                audioFile = t.audioFile()
                if audioFile != None:
                    start = self.calculateNextStart()
                    self.cueAudioFileOnTrack( audioFile, t.track(), start )
                    t.setLastCue(start)
                    
                    t.setIsPlaying(True)

                    nextTime = t.lastCue() + (audioFile.duration() * self.tempoScale())
                    
                    t.setEndPlayPoint(nextTime)
                    
                    # is looping enabled?
                    if t.isEnabled():                    
                        nextTime -= 1 # give it some time to cue
                        t.setNextOffset( nextTime )
                    
    
    def loopTrack(self, widget, track = None):
        loopActive = widget.get_active()
    
        index = track.index()
        for t in self.trackLoops:
            if t.track().index() == index:
                t.setEnabled(loopActive)

                if loopActive and t.isPlaying():
                    audioFile = t.audioFile()
                    if audioFile != None:                        
                        nextTime = t.lastCue() + (audioFile.duration() * self.tempoScale())
                        t.setEndPlayPoint(nextTime)
                        nextTime -= 1 # give it some time to cue
                        t.setNextOffset( nextTime )
                        
                    
    def changedCombo(self, combobox, trackLoop = None):
        model = combobox.get_model()
        index = combobox.get_active()
        if index:
            selFilename = model[index][0]

            path = trackLoop.loopPath()
            
            fullPath = join(path + selFilename)
            audioFile = self.audioFiles.add(fullPath)
            trackLoop.setAudioFile(audioFile)        
        
            
        return


    def volSliderChanged(self, widget, data = None):
        channelName = "track"+str(data)+"vol"
        self.eventSequencer.writeValueToChannel(channelName, widget.get_value())
        
    def createTrackView(self, trackLoop): 
        track = trackLoop.track()
        index = track.index()
        
        playButton = gtk.ToggleButton("play")
        playButton.set_size_request(100, 40)
        playButton.connect("clicked", self.playTrack, track)
        playButton.show()

        trackLoop.setPlayButton(playButton)
        
        loop = gtk.CheckButton("Loop")
        loop.set_active(False)
        loop.connect("clicked", self.loopTrack, track)
        loop.show()

        combobox = gtk.combo_box_new_text()
        combobox.show()
        combobox.append_text('Select a loop:')

        path = trackLoop.loopPath()
        for root, dirs, files in os.walk(path):
            for f in files:
                fullPath = join(path, f)
                self.audioFiles.add(fullPath)                
                combobox.append_text(f)
                time.sleep(0.1)

        combobox.connect('changed', self.changedCombo, trackLoop)
        combobox.set_active(1)

        # value, lower, upper, step_increment, page_increment, page_size
        volSlider = gtk.HScale(gtk.Adjustment(-6, -36, 6+1, 1.0, 1.0, 1.0))
        volSlider.connect("value_changed", self.volSliderChanged, index+1)
        volSlider.set_size_request(150, 30)
        volSlider.set_update_policy(gtk.UPDATE_CONTINUOUS)
        volSlider.set_digits(1)
        volSlider.set_value_pos(gtk.POS_BOTTOM)
        volSlider.set_draw_value(True)
        volSlider.show()

        pbar = gtk.ProgressBar()
        pbar.set_size_request(300, 40)
        pbar.show()
        trackLoop.setProgressBar(pbar)
        
        offset = 100
        height = 100
        y = (index * height) + offset

        self.layout.put(combobox, 10, y)        
        self.layout.put(volSlider, 10, y+50)                
        self.layout.put(playButton, 300, y)
        self.layout.put(loop, 420, y)
        self.layout.put(pbar, 520, y)

    def timeUpdate(self, message, userData = None):
        self.currentTime = message.argument(0) # current sequencer time
        gobject.idle_add(self.idleUpdateTime, self.currentTime)
        #self.time.set_label("%.3f" % self.currentTime)
        
        for t in self.trackLoops:
            if t.isPlaying():
                gobject.idle_add(self.idleUpdateProgress, t)
                #self.idleUpdateProgress(t)
                if t.isEnabled():
                    if self.currentTime > t.nextOffset():
                        start = self.calculateNextStart()
                        self.cueAudioFileOnTrack( t.audioFile(), t.track(), start )
                        t.setLastCue(start)
                        
                        nextTime = start + (t.audioFile().duration() * self.tempoScale())
                        t.setEndPlayPoint(nextTime)
                        nextTime -= 1 # give it some time to cue
                        t.setNextOffset( nextTime )

                if self.currentTime > t.endPlayPoint():
                    t.setIsPlaying(False)


    def tempoScale(self):
        return (self.eventSequencer._tempo / 60.)

    def idleUpdateTime(self, time):
        self.time.set_label("%.3f" % time)

    def idleUpdateProgress(self, t):

        progress = t.progressBar()
        audioFile = t.audioFile()        
        
        lastCue = t.lastCue() 
        if lastCue > self.currentTime:
            if t.isEnabled():
                # we must be looping - so make correction
                lastCue -= (audioFile.duration() * self.tempoScale())
                
        offset = self.currentTime - lastCue
        
        val = (audioFile.duration() * self.tempoScale())
        if val > 0:
            frac = offset / val
        else:
            frac = 0
        progress.set_fraction(frac)
                
        
    ############################################################################
    # Callbacks from CsoundEventSequencer
    ############################################################################
    def trackAdded(self, eventSequencer, track, requestRef, userData = None):

        
        index = track.index() + 1

        track.setInstrument(index)
        
        trackLoop = TrackLoop(track)
        self.trackLoops.append(trackLoop)
        
        
        # /home/olpc/Activities/CSRemixerActivity.activity
        self.sampleSetsPath = '/home/olpc/Activities/CSRemixerActivity.activity' +'/Sets'
        self.setName = 'Set1'
        
        if index == 1:
            trackLoop.setLoopPath(self.sampleSetsPath+'/'+self.setName+'/'+'Drums/')
        elif index == 2:
            trackLoop.setLoopPath(self.sampleSetsPath+'/'+self.setName+'/'+'FDelay/')
        elif index == 3:
            trackLoop.setLoopPath(self.sampleSetsPath+'/'+self.setName+'/'+'Textures/')
        elif index == 4:
            trackLoop.setLoopPath(self.sampleSetsPath+'/'+self.setName+'/'+'Vibes/')
                                    
        self.createTrackView(trackLoop)

class TrackLoop:
    def __init__(self, track):
        self._track = track
        self.setEnabled( False )
        self._nextOffset = -1
        self._audioFile = None
        self._lastCue = 0
        self._end = 0
        self._playButton = None
        self._isPlaying = False
        self._path = None
        self.pbar = None
        
    def setProgressBar(self, pbar): 
        self.pbar = pbar

    def progressBar(self):
        return self.pbar

    def setLoopPath(self, path):
        self._path = path

    def loopPath(self):
        return self._path
        
    def setEndPlayPoint(self, end):
        self._end = end
        
    def endPlayPoint(self):
        return self._end
            
    def setLastCue(self, cue):
        self._lastCue = cue

    def lastCue(self):
        return self._lastCue
        
    def track(self):
        return self._track

    def setPlayButton(self, playButton):
        self._playButton = playButton

    def setEnabled(self, enable):
        self._enable = enable

    def isEnabled(self):
        return self._enable
        
    def setAudioFile(self, audioFile):
        self._audioFile = audioFile

    def audioFile(self):
        return self._audioFile

    def setNextOffset(self, offset):
        self._nextOffset = offset
        
    def nextOffset(self):
        return self._nextOffset
        
    def setIsPlaying(self, flag):
        self._isPlaying = flag
        if self._playButton != None:
            self._playButton.set_active(flag)
            
    def isPlaying(self):
        return self._isPlaying

        
class AudioFiles:
    def __init__(self, csound):
        self.index = 1
        self.files = []
        self.csound = csound
        
    def add(self, filename):
        for f in self.files:
            if f.filename() == filename:
                return f
        
        file = AudioFile(self.csound, filename, self.index)
        file.load()
        
        self.index += 1
        self.files.append(file)
        return file
            
    def indexByFilename(self, filename):
        for f in self.files:
            if f.filename() == filename:
                return f.fTableIndex()

        return -1

    def audioFileByIndex(self, index):
        for f in self.files:
            if f.fTableIndex() == index:
                return f

        return None
        
class AudioFile:
    def __init__(self, csound, filename, fTable):
        self._filename = filename
        self._fTableIndex = fTable
        self.csound = csound
        self._tableLength = 0
        self._channels = 0
        self._sampleRate = 0
        self.isLoaded = False
        
    def filename(self):
        return self._filename

    def fTableIndex(self):
        return self._fTableIndex

    def tableLength(self):
        return self._tableLength

    def numChannels(self):
        return self._channels

    def sampleRate(self):
        return self._sampleRate
        
    def loadedCallback(self, message):
        """
        When the instrument finishes loading the f-table,
        this method should be called twice.  Once as /len
        and once as /channels ... once both values have been
        acquired, we can test them and decide if the table
        has been successfully loaded and then decide to play the sample
        if it was requested
        
        argument(0) will represent the length and number of channels respectively
        """
        #message.display("callback:")
        #print
        
        if message.currentPath() == '/len':
            self._tableLength = message.argument(0)
        elif message.currentPath() == '/channels':
            self._channels = message.argument(0)
        elif message.currentPath() == '/sr':
            self._sampleRate = message.argument(0)
        
        if self._tableLength > 0 and self._channels > 0 and self._sampleRate > 0:
            self.isLoaded = True

    def duration(self):
        if self._sampleRate > 0:
            return self._tableLength / self._sampleRate
        else:
            return 0
    
    def load(self):
        """
        Load the audio file as an f-table 
        The instrument will notify us when the table has loaded by calling
        self.loadedCallback
        """            
        self.loadCalled = True
        self.csound.registerChannel(str(self._fTableIndex), self.loadedCallback)
                        
        fEvent = 'i110 0 1 "'+self._filename+'" '+str(self._fTableIndex)

        self.csound.playEventAsString(fEvent)
