from wxPython.wx import * 
from wxPython.lib.dialogs import wxScrolledMessageDialog
from wxPython.grid import *
import os
import re
import os.path
import shutil
import zipfile
import ConfigParser
from select import select
from time import sleep
import keyinfo

###############
# TO DO:
#
#	* Saving, loading
#	* config file for csound call
#	* cut, copy, paste
#	* syntax highlighting
#	* when the user doesn't have a .ScoTracker directory (or any of the necessary files) it creates one
#	* configuration
#	* get some sleep
#
###############

# Call to csound
# esddsp csound -H1 -m0 -g -o devaudio -L stdin hb.orc

ID_MENU_ABOUT = wxNewId()
ID_MENU_OPEN = wxNewId()
ID_MENU_ADD = wxNewId()
ID_MENU_SAVE = wxNewId()
ID_MENU_CLOSE = wxNewId()
ID_MENU_EXIT = wxNewId()
ID_MENU_OPTIONS = wxNewId()
ID_NOTEBOOK = wxNewId()
ID_PANELHEADER = wxNewId()
ID_PANELPATTERNS = wxNewId()
ID_PANELINSTRUMENTS = wxNewId()
ID_PANELSAMPLES = wxNewId()
ID_CONFIG_POPUP = wxNewId()
ID_WAVE_LIST = wxNewId()
ID_STORED_LIST = wxNewId()
ID_BUTTON_LOAD = wxNewId()
ID_BUTTON_DELETE = wxNewId()
ID_BUTTON_CONVERT = wxNewId()
ID_BUTTON_PLAY = wxNewId()
ID_BUTTON_STOP = wxNewId()
ID_BUTTON_EDIT = wxNewId()
ID_BUTTON_HPLAY = wxNewId()
ID_BUTTON_HSTOP = wxNewId()
ID_BUTTON_HEDIT = wxNewId()
ID_BUTTON_SONGPLAY = wxNewId()
ID_BUTTON_SONGSTOP = wxNewId()
ID_BUTTON_NEWTRACK = wxNewId()
ID_BUTTON_TRACKPROPERTIES = wxNewId()
ID_TEXT_TRACKNAME = wxNewId()
ID_INST_NUMS = wxNewId()
ID_SPN_TEMPO = wxNewId()
ID_SPN_BARS = wxNewId()
ID_SPN_TICKS = wxNewId()
ID_SPN_BEATS = wxNewId()
ID_SPN_PATTERN = wxNewId()
ID_GRID_PATTERN = wxNewId()
ID_BTN_DONE = wxNewId()
ID_BTN_DELTRACK = wxNewId()
ID_CMB_TYPE = wxNewId()
ID_CMB_ASSC = wxNewId()

# global variable "working dir" for the CWD
workingDir = os.getcwd()

# global object trackInfo to hold all the info about this track
trackInfo = ConfigParser.ConfigParser()
# read in the track info
trackInfo.read(os.path.expanduser("~/.ScoTracker/working/trackinfo"))

# global object with configuration information
scoConfig = ConfigParser.ConfigParser()
# read in the config info
scoConfig.read(os.path.expanduser("~/.ScoTracker/config"))

# declare what files types we're allowed to see
filetypes = ["wav"]

# hash containing all our instruments names & numbers
instruments = {}

# array containing our tracktypes
trackType = []

# array containing what instrument our envelope track uses
envInst = []

# two dimensional array of strings of all note events
score = []

#---------------------------------------------------------------------------
# Misc subroutines
#---------------------------------------------------------------------------

def generateInstrumentList(comboBox, textarea):
	# delete all the existing instruments from the pulldown on the patterns page
	while comboBox.Number() > 0:
		comboBox.Delete(0)
	# remove all elements from the instruments array
	for insts in instruments.keys():
		del instruments[insts]
	# rebuild the instrument list for the patterns panel
	for matches in re.findall('(instr)(\W+)([0-9]+)([\W;]*)(.*?)(\n)', textarea.GetValue()):
		instruments[matches[2]] = matches[4]
		if (matches[2] != '999') and (matches[2] != '998'):
			# if we have an instrument name
			if matches[4] != '':
				# add it to the list
				comboBox.Append(matches[4])
			else:
				# otherwise add "instrument x"
				comboBox.Append("instrument " + matches[2])

#---------------------------------------------------------------------------
# class for the header info panel (first panel)
#---------------------------------------------------------------------------

class HeaderPanel(wxPanel):
	def __init__(self, parent):
		wxPanel.__init__(self, parent, -1)

		# make it so we can use the parent object
		self.parent = parent
		
		# make the components on the config sheet
		self.lblHeaderInfo = wxStaticText(self, -1, "Change the header information of your score:")

		# create a new spin control for tempo
		self.lblTempoInfo = wxStaticText(self, -1, "Global Tempo:")
		self.spnTempo = wxSpinCtrl(self, ID_SPN_TEMPO, min=0, max=320, initial=180, value="180")

		# create a new spin control for number of bars in this song
		self.lblBarsInfo = wxStaticText(self, -1, "Number of Bars in this song:")
		self.spnBars = wxSpinCtrl(self, ID_SPN_BARS, min=1, max=1024, initial=1, value="1")

		# spin control for the number of ticks per beat
		self.lblTicksInfo = wxStaticText(self, -1, "Ticks Per Beat:")
		self.spnTicks = wxSpinCtrl(self, ID_SPN_TICKS, min=0, max=16, initial=4, value="4")

		# spin control for the number of beats per bar
		self.lblBeatsInfo = wxStaticText(self, -1, "Beats Per Block (bar):")
		self.spnBeats = wxSpinCtrl(self, ID_SPN_BEATS, min=0, max=64, initial=16, value="16")

		# spin control events
		EVT_SPINCTRL(self.spnTempo, ID_SPN_TEMPO, self.OnSpnTempo)
		EVT_SPINCTRL(self.spnBars, ID_SPN_BARS, self.OnSpnBars)

		EVT_SPINCTRL(self.spnTicks, ID_SPN_TICKS, self.OnSpnTickOrBeat)
		EVT_SPINCTRL(self.spnBeats, ID_SPN_TICKS, self.OnSpnTickOrBeat)
		
		# create all the buttons at the top
		self.szTempoHolder = wxBoxSizer(wxHORIZONTAL)
		self.szTempoHolder.Add(self.lblTempoInfo, 0, wxLEFT, 0)
		self.szTempoHolder.Add(self.spnTempo, 0, wxLEFT, 5)
		self.szTempoHolder.Add(self.lblBarsInfo, 0, wxLEFT, 0)
		self.szTempoHolder.Add(self.spnBars, 0, wxLEFT, 5)

		# sizer to hold the number of ticks & beats
		self.szTicksHolder = wxBoxSizer(wxHORIZONTAL)
		self.szTicksHolder.Add(self.lblTicksInfo, 0, wxLEFT, 5)
		self.szTicksHolder.Add(self.spnTicks, 0, wxLEFT, 5)
		self.szTicksHolder.Add(self.lblBeatsInfo, 0, wxLEFT, 5)
		self.szTicksHolder.Add(self.spnBeats, 0, wxLEFT, 5)

		# add the instrument's text area
		# self.txtHeader = wxTextCtrl(self, -1, style = wxTE_MULTILINE|wxTE_AUTO_SCROLL|wxWANTS_CHARS|wxTE_PROCESS_ENTER)

		# make a text control which contains the wave files
		self.lstInstNums = wxListBox(self, ID_INST_NUMS, style=wxLB_SINGLE)
		self.lstStored = wxListBox(self, ID_STORED_LIST, style=wxLB_SINGLE)
		EVT_KEY_DOWN(self.lstStored, self.OnKeyHeaderWaves)
		EVT_LISTBOX(self.lstInstNums, ID_INST_NUMS, self.OnSelectNum)
		EVT_LISTBOX(self.lstStored, ID_STORED_LIST, self.OnSelectStored)

		# this list box allows the user to load in new samples
		self.lstWaves = wxListBox(self, ID_WAVE_LIST, style=wxLB_SINGLE)
		self.lstWaves.InsertItems(self.AudioFileList(), 0)
		# what happens when they double click a wave
		EVT_LISTBOX_DCLICK(self.lstWaves, ID_WAVE_LIST, self.OnDblClickWave)
		EVT_KEY_DOWN(self.lstWaves, self.OnKeyWaveList)

		# create a horizontal sizer for the wave sizer/text info
		self.lblHeader = wxStaticText(self, -1, "Score Header:")
		self.lblWaves = wxStaticText(self, -1, "Wave Files:")

		# create a new sizer to split the labels describing each section
		self.szTextInfo = wxBoxSizer(wxHORIZONTAL)
		self.szTextInfo.Add(self.lblHeader, 1, wxLEFT|wxEXPAND, 5)
		self.szTextInfo.Add(self.lblWaves, 1, wxLEFT|wxEXPAND, 5)

		# create a horizontal sizer for the wave viewer/listener
		self.szTextSplit = wxBoxSizer(wxHORIZONTAL)
		self.szTextSplit.Add(self.lstInstNums, 0, wxLEFT|wxALL|wxEXPAND, 5)
		self.szTextSplit.Add(self.lstStored, 1, wxLEFT|wxALL|wxEXPAND, 5)
		self.szTextSplit.Add(self.lstWaves, 1, wxLEFT|wxALL|wxEXPAND, 5)
		
		# make some shiny new buttons
		self.btnDelete = wxButton(self, ID_BUTTON_DELETE, "Delete")
		self.btnLoad = wxButton(self, ID_BUTTON_LOAD, "Load")
		#self.btnConvert = wxButton(self, ID_BUTTON_CONVERT, "Convert")
		# events for these buttons
		EVT_BUTTON(self.btnLoad, ID_BUTTON_LOAD, self.OnDblClickWave)
		EVT_BUTTON(self.btnDelete, ID_BUTTON_DELETE, self.OnDeleteBtn)
		
		# create a horizontal sizer for the left side wave buttons
		self.szButtonPanelLeft = wxBoxSizer(wxHORIZONTAL)
		self.szButtonPanelLeft.Add(self.btnDelete, 0, wxLEFT|wxRIGHT|wxALIGN_CENTRE, 5)

		# create a horizontal sizer for the right side wave buttons
		self.szButtonPanelRight = wxBoxSizer(wxHORIZONTAL)
		self.szButtonPanelRight.Add(self.btnLoad, 0, wxLEFT|wxRIGHT|wxALIGN_CENTRE, 5)
		#self.szButtonPanelRight.Add(self.btnConvert, 0, wxLEFT|wxRIGHT|wxALIGN_CENTRE, 5)

		# create a horizontal sizer for the wave action buttons
		self.szButtonPanels = wxBoxSizer(wxHORIZONTAL)
		self.szButtonPanels.Add(self.szButtonPanelLeft, 1, wxLEFT|wxBOTTOM, 5)
		self.szButtonPanels.Add(self.szButtonPanelRight, 1, wxLEFT|wxBOTTOM, 5)

		# create a new button
		self.btnHPlay = wxButton(self, ID_BUTTON_HPLAY, "Play")
		self.btnHStop = wxButton(self, ID_BUTTON_HSTOP, "Stop")
		self.btnHEdit = wxButton(self, ID_BUTTON_HEDIT, "Edit")
		# events for these buttons
		EVT_BUTTON(self.btnHPlay, ID_BUTTON_HPLAY, self.OnHPlayBtn)
		EVT_BUTTON(self.btnHStop, ID_BUTTON_HSTOP, self.OnStopBtn)
		EVT_BUTTON(self.btnHEdit, ID_BUTTON_HEDIT, self.OnHEditBtn)

		# create all the buttons at the top
		self.szButtonPanelLeft2 = wxBoxSizer(wxHORIZONTAL)
		self.szButtonPanelLeft2.Add(self.btnHPlay, 0, wxLEFT|wxRIGHT, 5)
		self.szButtonPanelLeft2.Add(self.btnHStop, 0, wxLEFT|wxRIGHT, 5)
		self.szButtonPanelLeft2.Add(self.btnHEdit, 0, wxLEFT|wxRIGHT, 5)

		# create a new button
		self.btnPlay = wxButton(self, ID_BUTTON_PLAY, "Play")
		self.btnStop = wxButton(self, ID_BUTTON_STOP, "Stop")
		self.btnEdit = wxButton(self, ID_BUTTON_EDIT, "Edit")
		# events for these buttons
		EVT_BUTTON(self.btnPlay, ID_BUTTON_PLAY, self.OnPlayBtn)
		EVT_BUTTON(self.btnStop, ID_BUTTON_STOP, self.OnStopBtn)
		EVT_BUTTON(self.btnEdit, ID_BUTTON_EDIT, self.OnEditBtn)

		# create all the buttons at the top
		self.szButtonPanelRight2 = wxBoxSizer(wxHORIZONTAL)
		self.szButtonPanelRight2.Add(self.btnPlay, 0, wxLEFT|wxRIGHT, 5)
		self.szButtonPanelRight2.Add(self.btnStop, 0, wxLEFT|wxRIGHT, 5)
		self.szButtonPanelRight2.Add(self.btnEdit, 0, wxLEFT|wxRIGHT, 5)

		# create a horizontal sizer for the wave action buttons
		self.szButtonPanels2 = wxBoxSizer(wxHORIZONTAL)
		self.szButtonPanels2.Add(self.szButtonPanelLeft2, 1, wxLEFT|wxBOTTOM, 5)
		self.szButtonPanels2.Add(self.szButtonPanelRight2, 1, wxLEFT|wxBOTTOM, 5)

		# and do the layout
		self.sizer = wxBoxSizer(wxVERTICAL)
		self.sizer.Add(self.lblHeaderInfo, 0, wxEXPAND|wxTOP|wxLEFT, 5)
		self.sizer.Add(self.szTempoHolder, 0, wxTOP|wxLEFT|wxALL, 5)
		self.sizer.Add(self.szTicksHolder, 0, wxTOP|wxLEFT|wxALL, 5)
		self.sizer.Add(self.szTextInfo, 0, wxTOP|wxLEFT|wxALL|wxEXPAND, 5)
		self.sizer.Add(self.szTextSplit, 1, wxTOP|wxLEFT|wxALL|wxEXPAND, 5)
		self.sizer.Add(self.szButtonPanels, 0, wxTOP|wxLEFT|wxALL|wxEXPAND, 5)
		self.sizer.Add(self.szButtonPanels2, 0, wxTOP|wxLEFT|wxALL|wxEXPAND, 5)

		# make it a nice auto layout thingie
		self.SetSizer(self.sizer)
		self.SetAutoLayout(true)

	##########
	# EVENTS #
	##########

	def OnSpnTempo(self, event):
		pass
		
	def OnSpnBars(self, event):
		tempnewsize = self.spnTicks.GetValue() * self.spnBeats.GetValue() * self.spnBars.GetValue()
		# if we've reduced the number of bars
		if len(score[0]) > tempnewsize:
			for i in range(len(score)):
				while len(score[i]) > tempnewsize:
					del(score[i][-1])
		# if we've increased the number of bars
		if len(score[0]) < tempnewsize:
			for i in range(len(score)):
				while len(score[i]) < tempnewsize:
					score[i].append("")
		# make sure we give the pattern scroller the right max and min
		self.parent.panelPattern.spnPattern.SetRange(0, len(score[0])/(self.spnBeats.GetValue()*self.spnTicks.GetValue()) - 1)
				
	# if they change the number of ticks or beats per thing
	def OnSpnTickOrBeat(self, event):
		self.parent.panelPattern.updateRowNames(0, self.spnTicks.GetValue(), self.spnBeats.GetValue())
		self.parent.panelPattern.updateRowColors(0, self.spnTicks.GetValue(), self.spnBeats.GetValue())
		# make sure we give the pattern scroller the right max and min
		self.parent.panelPattern.spnPattern.SetRange(0, len(score[0])/(self.spnBeats.GetValue()*self.spnTicks.GetValue()) - 1)
	
	# when an table number is selected, select the corresponding instrument
	def OnSelectNum(self, event):
		self.lstStored.SetSelection(self.lstInstNums.GetSelection(), true)

	# when an instrument is selected, select the corresponding table number
	def OnSelectStored(self, event):
		self.lstInstNums.SetSelection(self.lstStored.GetSelection(), true)

	# delete button was pressed
	def OnDeleteBtn(self, event):
		# which file did they select?
		selectedFile = self.lstStored.GetStringSelection()
		# remove this file from the list entry and instrument number list
		deleteWhich = self.lstStored.FindString(selectedFile)
		self.lstStored.Delete(deleteWhich)
		self.lstInstNums.Delete(deleteWhich)
		# remove the sample file from our temp directory
		os.unlink(os.path.join(os.path.expanduser("~/.ScoTracker/working"), selectedFile))
		# figure out which fnum we want from those stored in the options
		for option in trackInfo.options("samples"):
			if (trackInfo.get("samples", option) == selectedFile):
				# remove this option
				trackInfo.remove_option("samples", option)
				# tell Csound to clear out that instrument
				csIO.sendText(option + " 0 2 -2 0 0 \n")

	# stop button was pressed
	def OnStopBtn(self, event):
		# tell Csound to stop playing our special instrument
		csIO.sendText("i999 0 0\n")

	# play button was pressed
	def OnPlayBtn(self, event):
		# see which file they selected
		selectedFile = self.lstWaves.GetStringSelection()
		csIO.sendText("f999 0 0 -1 \"" + workingDir + "/" + selectedFile + "\" 0 0 0\n")
		csIO.sendText("i999 0 -1 " + str(keyinfo.get_octave() + 1) + ".06 " + " 999\n")

	# delete button was pressed
	def OnEditBtn(self, event):
		selectedFile = self.lstWaves.GetStringSelection()
		csIO.stopCS();
		os.system("sweep '" + os.path.normpath(os.path.join(workingDir, selectedFile)) + "'")
		csIO.startCS();

	# delete button was pressed
	def OnHPlayBtn(self, event):
		# see which file they selected
		selectedFile = self.lstStored.GetStringSelection()
		# figure out which fnum we want from those stored in the options
		for option in trackInfo.options("samples"):
			if (trackInfo.get("samples", option) == selectedFile):
				fNum = option[1:]
				# tell Csound to play the new bozounga
				csIO.sendText("i999 0 -1 " + str(keyinfo.get_octave() + 1) + ".06 " + fNum + "\n")

	# delete button was pressed
	def OnHEditBtn(self, event):
		selectedFile = self.lstStored.GetStringSelection()
		csIO.stopCS();
		os.system("sweep '" + os.path.join(os.path.expanduser("~/.ScoTracker/working"), selectedFile) + "'")
		csIO.startCS();
		
	# double click a file name event
	def OnDblClickWave(self, event):
		# declare workingDir as global - can't figure out why i have to do this.
		# must learn more about python's scoping
		global workingDir
		# see which file they selected
		selectedFile = self.lstWaves.GetStringSelection()
		# if the file is a directory, change to it, and list all the files
		if os.path.isdir(os.path.join(workingDir, selectedFile)):
			workingDir = os.path.normpath(os.path.join(workingDir, selectedFile))
			self.lstWaves.Clear()
			self.lstWaves.InsertItems(self.AudioFileList(), 0)
		# otherwise, move the file into our list of wav files
		elif (selectedFile.endswith(".wav")):
			add = true
			# test that the file name isn't already in our list of wavs
			for ItemNumber in range(0, self.lstStored.Number()):
				if (selectedFile == self.lstStored.GetString(ItemNumber)):
					add = false
			# if it isn't, then add it to our list, and copy the file to our working directory, create a new entry in the info
			if (add):
				# add our new audio file to the list of samples
				self.lstStored.Append(selectedFile)
				# copy our new audio file to the temp directory
				shutil.copyfile(os.path.normpath(os.path.join(workingDir, selectedFile)), os.path.join(os.path.expanduser("~/.ScoTracker/working"), selectedFile))
				# make sure we have a unique table number
				self.optionCounter = 1
				while (trackInfo.has_option("samples", "f" + str(self.optionCounter))):
					self.optionCounter = self.optionCounter + 1
				# make an entry in the instrument numbers table
				self.lstInstNums.Append("f" + str(self.optionCounter))
				# make an entry in the options file
				trackInfo.set("samples", "f" + str(self.optionCounter), selectedFile)
				# tell Csound to load the new audio file
				csIO.sendText("f" + str(self.optionCounter) + " 0 0 -1 \"" + os.path.join(os.path.expanduser("~/.ScoTracker/working"), selectedFile) + "\" 0 0 0\n")
		else:
			# if the file is a wav file
			dlgErr = wxMessageDialog(self, "You can only import .wav files", "Error", wxOK)
			dlgErr.ShowModal()
			dlgErr.Destroy()
			
	
	# if they press a key (to play a sample at a pitch) in the wave file list
	def OnKeyWaveList(self, evt):
		# see which file they selected
		selectedFile = self.lstWaves.GetStringSelection()
		if keyinfo.has_note(evt.KeyCode()):
			csIO.sendText("f999 0 0 -1 \"" + workingDir + "/" + selectedFile + "\" 0 0 0\n")
			csIO.sendText("i999 0 -1 " + keyinfo.get_note(evt.KeyCode()) + " 999\n")

	# if they press a key (to play a sample at a pitch) in the wave file list
	def OnKeyHeaderWaves(self, evt):
		# see which file they selected
		selectedFile = self.lstStored.GetStringSelection()
		if keyinfo.has_note(evt.KeyCode()):
			# figure out which fnum we want from those stored in the options
			for option in trackInfo.options("samples"):
				if (trackInfo.get("samples", option) == selectedFile):
					fNum = option[1:]
					# tell Csound to play the new bozounga
					csIO.sendText("i999 0 -1 " + keyinfo.get_note(evt.KeyCode()) + " " + fNum + "\n")

	# routine to get a list of audio files in this directory
	def AudioFileList(self):
		# initialize our output list to just the default '..'
		filelist = []
		filelist.append("..")
		# sort the directory listing
		allFiles = os.listdir(workingDir)
		allFiles.sort()
		# look through each file name in the current directory
		for filename in allFiles:
			# if it's a directory, put it in the list with a '/' character on the end
			if os.path.isdir(workingDir + "/" + filename):
				filelist.append(filename + "/")
			# if it's in the list of known extensions, then put it in the list
			for filetype in filetypes:
				if filename.endswith(filetype):
					filelist.append(filename)
		return filelist

	def loadLast(self):
		# cycle through the samples creating entries in our header panel
		for option in trackInfo.options("samples"):
			self.lstStored.Append(trackInfo.get("samples", option))
			self.lstInstNums.Append(option)

	def writeSamples(self):
		# write out the list of samples we're using
		infoFile = open(os.path.expanduser("~/.ScoTracker/working/trackinfo"), 'w')
		trackInfo.write(infoFile)
		infoFile.close()

#---------------------------------------------------------------------------
# class for my instruments panel
#---------------------------------------------------------------------------

class InstrumentPanel(wxPanel):
	def __init__(self, parent):
		wxPanel.__init__(self, parent, -1)

		# make sure we have access to the parent
		self.parent = parent
		
		# make the components on the config sheet
		self.lblHeaderInfo = wxStaticText(self, -1, "Create Instruments:")		

		# add the instrument's text area
		self.txtInstruments = wxTextCtrl(self, -1, style = wxTE_MULTILINE|wxTE_AUTO_SCROLL|wxWANTS_CHARS|wxTE_PROCESS_ENTER)
		
		# and do the layout
		self.sizer = wxBoxSizer(wxVERTICAL)
		self.sizer.Add(self.lblHeaderInfo, 0, wxEXPAND|wxTOP|wxALL|wxLEFT, 5)
		self.sizer.Add(self.txtInstruments, 1, wxEXPAND|wxTOP|wxALL|wxLEFT, 5)

		# make it a nice auto layout thingie
		self.SetSizer(self.sizer)
		self.SetAutoLayout(true)

		# load in all our instruments on creation
		self.loadInstruments()

		# set up our KILLFOCUS event
		EVT_KILL_FOCUS(self.txtInstruments, self.lostFocus)
		
		# remember which instrument they selected
		whichSelected = self.parent.panelPattern.cmbInst.GetSelection()
		# make sure we've got the instrument list whent the program first starts
		generateInstrumentList(self.parent.panelPattern.cmbInst, self.txtInstruments)
		# re select the instrument previously selected
		self.parent.panelPattern.cmbInst.SetSelection(whichSelected)

		# check for new messages every 10th of a second (a bit of a kludge)
		csIO.Start(100)

	def loadInstruments(self):
		self.txtInstruments.LoadFile(os.path.expanduser("~/.ScoTracker/working/instruments.orc"))

	def saveInstruments(self):
		self.txtInstruments.SaveFile(os.path.expanduser("~/.ScoTracker/working/instruments.orc"))
		
	def lostFocus(self, event):
		# if the instruments text has been modified
		if (self.txtInstruments.IsModified()):
			# save the instruments
			self.saveInstruments()
			# restart Csound instance
			csIO.stopCS()
			csIO.startCS()
			# remember which instrument they selected
			whichSelected = self.parent.panelPattern.cmbInst.GetSelection()
			# make sure we've got the instrument list whent the program first starts
			generateInstrumentList(self.parent.panelPattern.cmbInst, self.txtInstruments)
			# re select the instrument previously selected
			self.parent.panelPattern.cmbInst.SetSelection(whichSelected)
		# make sure all the rest of the events get processed as per normal
		event.Skip()
		
#---------------------------------------------------------------------------
# class for my patterns grid
#---------------------------------------------------------------------------

class PatternPanel(wxPanel):
	def __init__(self, parent):
		wxPanel.__init__(self, parent, -1)
		self.Keysdown = []
		
		# make the entire notebook available to me
		self.parent = parent
		
		# make the components on the config sheet
		self.lblHeaderInfo = wxStaticText(self, -1, "Program your score:")		

		# create our combo box of instruments
		self.cmbInst = wxComboBox(self, -1, style=wxCB_DROPDOWN|wxCB_READONLY)	
		self.cmbInst.SetSize([250, -1])

		# create a horizontal sizer for the left side wave buttons
		self.szButtonPanel = wxBoxSizer(wxHORIZONTAL)
		self.szButtonPanel.Add(self.cmbInst, 0, wxLEFT|wxRIGHT|wxALIGN_CENTRE, 5)

		# make our track control buttons
		self.lblTracks = wxStaticText(self, -1, "Track:")
		self.btnNewTrack = wxButton(self, ID_BUTTON_NEWTRACK, "New")
		self.btnTrackProperties = wxButton(self, ID_BUTTON_TRACKPROPERTIES, "Properties")

		self.btnPlay = wxButton(self, ID_BUTTON_SONGPLAY, "Play")
		self.btnStop = wxButton(self, ID_BUTTON_SONGSTOP, "Stop")

		# spin control for what pattern we're on
		self.spnPattern = wxSpinCtrl(self, ID_SPN_PATTERN, min=0, max=0, initial=0, value="0")
		EVT_SPINCTRL(self.spnPattern, ID_SPN_PATTERN, self.OnSpnPattern)
		
		# events for these buttons
		EVT_BUTTON(self.btnNewTrack, ID_BUTTON_NEWTRACK, self.OnBtnNewTrack)
		EVT_BUTTON(self.btnTrackProperties, ID_BUTTON_TRACKPROPERTIES, self.OnBtnTrackProperties)
		EVT_BUTTON(self.btnPlay, ID_BUTTON_SONGPLAY, self.OnBtnPlay)
		EVT_BUTTON(self.btnStop, ID_BUTTON_SONGSTOP, self.OnBtnStop)
		EVT_GRID_LABEL_RIGHT_CLICK(self, self.OnLabelRightClick)

		# create a horizontal sizer for the left side wave buttons
		self.szTrackBttnPanel = wxBoxSizer(wxHORIZONTAL)
		self.szTrackBttnPanel.Add(self.lblTracks, 0, wxLEFT|wxRIGHT|wxALIGN_CENTRE, 5)
		self.szTrackBttnPanel.Add(self.btnNewTrack, 0, wxLEFT|wxRIGHT|wxALIGN_CENTRE, 5)
		self.szTrackBttnPanel.Add(self.btnTrackProperties, 0, wxLEFT|wxRIGHT|wxALIGN_CENTRE, 5)
		self.szTrackBttnPanel.Add(self.btnPlay, 0, wxLEFT|wxRIGHT|wxALIGN_CENTRE, 5)
		self.szTrackBttnPanel.Add(self.btnStop, 0, wxLEFT|wxRIGHT|wxALIGN_CENTRE, 5)
		self.szTrackBttnPanel.Add(self.spnPattern, 0, wxLEFT|wxRIGHT|wxALIGN_CENTRE, 5)

		# Create our grid which will hold all of our patterns
		self.grdPattern = wxGrid(self, ID_GRID_PATTERN);
		self.grdPattern.DisableDragGridSize()

		# events for our grid
		# EVT_GRID_LABEL_RIGHT_CLICK(self.grdPattern, self.OnLabelRightClick)
		EVT_KEY_DOWN(self.grdPattern, self.OnKeyDown)
		EVT_KEY_UP(self.grdPattern, self.OnKeyUp)
		EVT_GRID_CELL_CHANGE(self.grdPattern, self.CellChange)

		# Do the layout
		self.sizer = wxBoxSizer(wxVERTICAL)
		self.sizer.Add(self.lblHeaderInfo, 0, wxEXPAND|wxTOP|wxALL|wxLEFT, 5)
		self.sizer.Add(self.szButtonPanel, 0, wxEXPAND|wxTOP|wxALL|wxLEFT, 5)
		self.sizer.Add(self.szTrackBttnPanel, 0, wxEXPAND|wxTOP|wxALL|wxLEFT, 5)
		self.sizer.Add(self.grdPattern, 1, wxEXPAND|wxTOP|wxALL|wxLEFT, 5)

		# make it a nice auto layout thingie
		self.SetSizer(self.sizer)
		self.SetAutoLayout(true)

		# create a new sequencer object
		self.seq = Sequencer(self.parent)

		# tell the Csound IO object which sequencer to use
		csIO.setupSequencer(self.seq)

	##########
	# EVENTS #
	##########

	# if the user moves to a different cell, check that we're synchronized with the score
	def CellChange(self, event):
		# print str(event.GetRow()) + " " + str(event.GetCol())
		score[event.GetCol()][event.GetRow() + self.parent.panelHeader.spnBeats.GetValue() * self.parent.panelHeader.spnTicks.GetValue() * self.spnPattern.GetValue()] = self.grdPattern.GetCellValue(event.GetRow(), event.GetCol())

	# If a key is pressed on the pattern
	def OnKeyDown(self, evt):
		# check whether this is a note track or not
		if (trackType[self.grdPattern.GetGridCursorCol()] == 'note'):
			# figure out which instrument they just pressed
			for instNum in instruments.keys():
				if (self.cmbInst.GetStringSelection() == "instrument " + instNum) or (instruments[instNum] == self.cmbInst.GetStringSelection()):
					whichInstrument = instNum
			# Make sure it's not a navigation key and enter it into the grid with what instrument was pressed
			if keyinfo.has_value(evt.KeyCode()):
				# figure out if it's a 2 character wide number or not
				if (int(whichInstrument) <= 9):
					insertSpace = ' '
				else:
					insertSpace = ''
				self.grdPattern.SetCellValue(self.grdPattern.GetGridCursorRow(), self.grdPattern.GetGridCursorCol(), keyinfo.get_value(evt.KeyCode()) + " (" + str(whichInstrument) + insertSpace + ")")
				score[self.grdPattern.GetGridCursorCol()][self.grdPattern.GetGridCursorRow() + self.parent.panelHeader.spnBeats.GetValue() * self.parent.panelHeader.spnTicks.GetValue() * self.spnPattern.GetValue()] = keyinfo.get_value(evt.KeyCode()) + " (" + str(whichInstrument) + insertSpace + ")"
			else:
				evt.Skip()
			# if they press '=' put a stop note in
			if evt.KeyCode() == 61:
				self.grdPattern.SetCellValue(self.grdPattern.GetGridCursorRow(), self.grdPattern.GetGridCursorCol(), "==")
				score[self.grdPattern.GetGridCursorCol()][self.grdPattern.GetGridCursorRow() + self.parent.panelHeader.spnBeats.GetValue() * self.parent.panelHeader.spnTicks.GetValue() * self.spnPattern.GetValue()] = "=="
			# if there's a note pertaining to this key and the key isn't being played yet
			if keyinfo.has_note(evt.KeyCode()) and not (whichInstrument in self.Keysdown):
				csIO.sendText("i" + whichInstrument + " 0 -1 " + keyinfo.get_note(evt.KeyCode()) + " " + whichInstrument + "\n")
				self.Keysdown.append(whichInstrument)
			# detect the key values (development)
			# print evt.KeyCode()
		# if they press the 'copy' key (ctrl-C)
		if evt.KeyCode() == 22:
			print "paste"
		# if they press the 'cut' key (ctrl-X)
		if evt.KeyCode() == 3:
			print "copy"
		# if they press the 'paste' key (ctrl-V)
		if evt.KeyCode() == 24:
			print "cut"
		# if they press 'delete', remove this event
		if evt.KeyCode() == 127:
			# if there are any cells selected
			if self.grdPattern.IsSelection():
				# cycle through all rows and columns
				for row in range(self.grdPattern.GetNumberRows()):
					for col in range(self.grdPattern.GetNumberCols()):
						# if this cell is selected
						if self.grdPattern.IsInSelection(row, col):
							# delete it
							self.grdPattern.SetCellValue(row, col, "")
							score[col][row + self.parent.panelHeader.spnBeats.GetValue() * self.parent.panelHeader.spnTicks.GetValue() * self.spnPattern.GetValue()] = ""
			else:
				# just delete the cell we're on
				self.grdPattern.SetCellValue(self.grdPattern.GetGridCursorRow(), self.grdPattern.GetGridCursorCol(), "")
				score[self.grdPattern.GetGridCursorCol()][self.grdPattern.GetGridCursorRow() + self.parent.panelHeader.spnBeats.GetValue() * self.parent.panelHeader.spnTicks.GetValue() * self.spnPattern.GetValue()] = ""

	# If a key is let go on the pattern
	def OnKeyUp(self, evt):
		# check whether this is a note track or not
		if (trackType[self.grdPattern.GetGridCursorCol()] == 'note'):
			# figure out which key they just let up
			for instNum in instruments.keys():
				if (self.cmbInst.GetStringSelection() == "instrument " + instNum) or (instruments[instNum] == self.cmbInst.GetStringSelection()):
					whichInstrument = instNum
			# Make sure it's not a navigation key
			if not keyinfo.has_value(evt.KeyCode()):
				evt.Skip()
			# if there's a note pertaining to this key and that note is actually being played
			if keyinfo.has_note(evt.KeyCode()) and (whichInstrument in self.Keysdown):
				csIO.sendText("i" + str(whichInstrument) + " 0 0 " + keyinfo.get_note(evt.KeyCode()) + " " + str(whichInstrument) + "\n")
				del(self.Keysdown[self.Keysdown.index(whichInstrument)])
			# detect the key values (development)
			# print evt.KeyCode()

	# pattern number spin control
	def OnSpnPattern(self, evt):
		self.setPattern(self.parent.panelHeader.spnTicks.GetValue())

	# update the contents of the grid to our current pattern
	def setPattern(self, where):
		barsize = self.parent.panelHeader.spnBeats.GetValue() * self.parent.panelHeader.spnTicks.GetValue()
		for col in range(len(score)):
			for row in range(barsize):
				self.grdPattern.SetCellValue(row, col, score[col][row + self.spnPattern.GetValue() * barsize])
		pass

	# Right clicking on the track label
	def OnLabelRightClick(self, event):
		self.trackConfig(event.GetCol())

	# If play is pressed
	def OnBtnPlay(self, evt):
		self.seq.StartSequencer(self.parent.panelHeader.spnTempo.GetValue(), self.grdPattern.GetGridCursorRow() + self.spnPattern.GetValue() * self.parent.panelHeader.spnBeats.GetValue() * self.parent.panelHeader.spnTicks.GetValue())

	# what happens if they press stop
	def OnBtnStop(self, evt):
		self.seq.StopSequencer()

	# Clicking on the 'track properties' button
	def OnBtnTrackProperties(self, evt):
		self.trackConfig(self.grdPattern.GetGridCursorCol())

	def OnBtnNewTrack(self, event):
		# my pants are on fire!
		self.grdPattern.AppendCols(1, TRUE)
		trackType.append('note')
		envInst.append(self.cmbInst.GetStringSelection())
		# make sure the score is synchronized
		tempscore = []
		tempscore.append("")
		while (len(tempscore) < len(score[0])):
			tempscore.append("")
		score.append(tempscore)
		# because the default instrument is a note type, set them read only
		for row in range(self.grdPattern.GetNumberRows()):
			self.grdPattern.SetReadOnly(row, self.grdPattern.GetNumberCols() - 1, true)
		# reset the names and colours of the whole thing
		self.updateRowColors(0, self.parent.panelHeader.spnTicks.GetValue(), self.parent.panelHeader.spnBeats.GetValue())
	
	def updateRowNames(self, startRow, ticksPerBeat, beatsPerBlock):
		# for every row
		# name it with pattern:beat:tick format (depending on number of beats per pattern and ticks per beat)
		# cycle through our grid, making sure everything is named properly
		for row in range(self.grdPattern.GetNumberRows()):
			self.grdPattern.SetRowLabelValue(row, str(int(row / (ticksPerBeat * beatsPerBlock))) + ' : ' + str(hex(int(row / ticksPerBeat) % beatsPerBlock)[-1:]) + '.' + str(hex(row % ticksPerBeat))[-1:])
		# make sure the grid is updated graphically speaking
		self.grdPattern.ForceRefresh()

	def updateTrackProperties(self):
		# for every row and column
		for col in range(self.grdPattern.GetNumberCols()):
			for row in range(self.grdPattern.GetNumberRows()):
				# if the type is a note
				if trackType[col] == 'note':
					self.grdPattern.SetReadOnly(row, col, true)
				# if the type is an envelope
				if trackType[col] == 'envelope':
					self.grdPattern.SetReadOnly(row, col, false)

	def updateRowColors(self, startRow, ticksPerBeat, beatsPerBlock):
		# for every row
		# cycle through our grid, making sure everything is coloured properly
		for row in range(self.grdPattern.GetNumberRows()):
			# if we're at the end of a block
			if not (row % (beatsPerBlock * ticksPerBeat)):
				# colour all the row backgrounds yellow
				for col in range(self.grdPattern.GetNumberCols()):
					self.grdPattern.SetCellBackgroundColour(row, col, "#FFAABB")
			# every time we're at the next beat
			elif not (row % ticksPerBeat):
				# colour all the row backgrounds grey
				for col in range(self.grdPattern.GetNumberCols()):
					self.grdPattern.SetCellBackgroundColour(row, col, wxLIGHT_GREY)
			else:
				# colour all the row backgrounds grey
				for col in range(self.grdPattern.GetNumberCols()):
					self.grdPattern.SetCellBackgroundColour(row, col, wxWHITE)
		# make sure the grid is updated graphically speaking
		self.grdPattern.ForceRefresh()
		
	def trackConfig(self, whichcol):
		# launch the config popup
		pop = MyConfigPopup(self, -1, "Track Configuration", whichcol)
		pop.Show(true)
		return true

	def writeTracks(self):
		# create a config object with all of our tracks
		tracks = ConfigParser.ConfigParser()
		# set up some info about our tracks
		tracks.add_section("defaultInfo")
		tracks.set("defaultInfo", "rows", len(score[0]))
		tracks.set("defaultInfo", "cols", len(score))
		tracks.set("defaultInfo", "bars", self.parent.panelHeader.spnBars.GetValue())
		tracks.set("defaultInfo", "tempo", self.parent.panelHeader.spnTempo.GetValue())
		tracks.set("defaultInfo", "ticks", self.parent.panelHeader.spnTicks.GetValue())
		tracks.set("defaultInfo", "beats", self.parent.panelHeader.spnBeats.GetValue())

		# go through each column and each row in the pattern storing the details
		for col in range(len(score)):
			collabel = self.grdPattern.GetColLabelValue(col)
			tracks.add_section(collabel)
			tracks.set(collabel, "position", str(col))
			tracks.set(collabel, "width", self.grdPattern.GetColSize(col))
			tracks.set(collabel, "type", trackType[col])
			tracks.set(collabel, "inst", envInst[col])
			for row in range(len(score[0])):
				if (score[col][row] != ''):
					tracks.set(collabel, str(row), score[col][row])
		# write out our current tracks to disk
		trackFile = open(os.path.expanduser("~/.ScoTracker/working/tracks"), 'w')
		tracks.write(trackFile)
		trackFile.close()

	def loadLast(self):
		# create a config object with all of our tracks
		tracks = ConfigParser.ConfigParser()
		# read in the track info
		tracks.read(os.path.expanduser("~/.ScoTracker/working/tracks"))
		# while the number of tracks etc. in the score is less than those saved, add them
		self.parent.panelHeader.spnBars.SetValue(int(tracks.get("defaultInfo", "bars")))
		self.parent.panelHeader.spnTempo.SetValue(int(tracks.get("defaultInfo", "tempo")))
		self.parent.panelHeader.spnTicks.SetValue(int(tracks.get("defaultInfo", "ticks")))
		self.parent.panelHeader.spnBeats.SetValue(int(tracks.get("defaultInfo", "beats")))
		# use the default info to set up the number of tracks and length of the song
		self.grdPattern.CreateGrid(int(tracks.get("defaultInfo", "ticks")) * int(tracks.get("defaultInfo", "beats")), int(tracks.get("defaultInfo", "cols")))
		# stretch our default score to be big enough (in both dimensions)
		while (len(score) < int(tracks.get("defaultInfo", "cols"))):
			tempscore = []
			tempscore.append("")
			while (len(tempscore) < int(tracks.get("defaultInfo", "rows"))):
				tempscore.append("")
			score.append(tempscore)
		# make sure the default doesn't throw us out
		tracks.remove_section("defaultInfo")
		# cycle through each track creating entries in our grid
		for trackName in tracks.sections():
			# make sure the label and width was the same as before we quit
			self.grdPattern.SetColLabelValue(int(tracks.get(trackName, "position")), trackName)
			self.grdPattern.SetColSize(int(tracks.get(trackName, "position")), int(tracks.get(trackName, "width")))
			# make sure our tracktype is as wide as
			while (len(trackType) <= int(tracks.get(trackName, "position"))):
				trackType.append("")
			trackType[int(tracks.get(trackName, "position"))] = tracks.get(trackName, "type")
			while (len(envInst) <= int(tracks.get(trackName, "position"))):
				envInst.append("")
			envInst[int(tracks.get(trackName, "position"))] = tracks.get(trackName, "inst")
			# for every line in this track
			for option in tracks.options(trackName):
				# make sure we're only getting numbers
				try:
					lineNumber = int(option)
				except:
					pass
				else:
					score[int(tracks.get(trackName, "position"))][lineNumber] = tracks.get(trackName, option)
		# make the number of patterns in the song equal to the number of bars
		self.spnPattern.SetRange(0, len(score[0])/(self.parent.panelHeader.spnBeats.GetValue()*self.parent.panelHeader.spnTicks.GetValue()) - 1)
		# make sure we have all the right types of tracks setup
		self.updateTrackProperties()
		# make sure our rows are named correctly to start off with
		self.updateRowNames(0, self.parent.panelHeader.spnTicks.GetValue(), self.parent.panelHeader.spnBeats.GetValue())
		# make sure the rows are colored correctly to start off with
		self.updateRowColors(0, self.parent.panelHeader.spnTicks.GetValue(), self.parent.panelHeader.spnBeats.GetValue())
		# put the user at the start of the song
		self.setPattern(0)

#---------------------------------------------------------------------------
# Class for the config popup
#---------------------------------------------------------------------------
class MyConfigPopup(wxFrame):
	def __init__(self, parent, ID, title, whichCol):
		wxFrame.__init__(self, parent, ID, title, wxDefaultPosition)

		# make sure we can access what called us
		self.parent = parent
		self.whichCol = whichCol

		# make the components on the config sheet
		self.label = wxStaticText(self, -1, "Channel Name:")
		
		# make a new text control to enter the track name
		self.text = wxTextCtrl(self, ID_TEXT_TRACKNAME)
		self.text.SetValue(parent.grdPattern.GetColLabelValue(whichCol))
		EVT_TEXT(self.text, ID_TEXT_TRACKNAME, self.OnNameChanged)

		# make the components on the config sheet
		self.lblDrop = wxStaticText(self, -1, "Channel Type:")

		# combo box: what kind of track it is
		self.cmbType = wxComboBox(self, ID_CMB_TYPE, style=wxCB_DROPDOWN|wxCB_READONLY, choices=["note", "envelope"])
		self.cmbType.SetSelection(self.cmbType.FindString(trackType[self.whichCol]))
		EVT_COMBOBOX(self.cmbType, ID_CMB_TYPE, self.OnTypeChange)

		# make the components on the config sheet
		self.lblInst = wxStaticText(self, -1, "Associated Instrument:")

		# combo box: what kind of track it is
		self.cmbAssc = wxComboBox(self, ID_CMB_ASSC, style=wxCB_DROPDOWN|wxCB_READONLY)	
		# get the latest list of instruments
		generateInstrumentList(self.cmbAssc, self.parent.parent.panelInstruments.txtInstruments)
		# select the one we like
		self.cmbAssc.SetSelection(self.cmbAssc.FindString(envInst[self.whichCol]))
		EVT_COMBOBOX(self.cmbAssc, ID_CMB_ASSC, self.OnAsscChange)
		# disabl this if we're using a note event
		if (trackType[self.whichCol] == 'note'):
			self.cmbAssc.Enable(FALSE)

		# done button
		self.btnDone = wxButton(self, ID_BTN_DONE, "Done")
		EVT_BUTTON(self.btnDone, ID_BTN_DONE, self.OnBtnDone)

		# delete button
		self.btnDel = wxButton(self, ID_BTN_DELTRACK, "Delete")
		EVT_BUTTON(self.btnDel, ID_BTN_DELTRACK, self.OnBtnDel)

		# create a new horizontal sizer for these buttons
		self.btnSize = wxBoxSizer(wxHORIZONTAL)
		self.btnSize.Add(self.btnDel, 1, wxLEFT|wxRIGHT|wxALIGN_CENTER|wxBOTTOM, 5)
		self.btnSize.Add(self.btnDone, 1, wxLEFT|wxRIGHT|wxALIGN_CENTER|wxBOTTOM, 5)

		# and do the layout
		self.sizer = wxBoxSizer(wxVERTICAL)
		self.sizer.Add(self.label, 0, wxEXPAND|wxTOP|wxLEFT, 5)
		self.sizer.Add(self.text, 0, wxTOP|wxLEFT|wxALL|wxEXPAND, 5)
		self.sizer.Add(self.lblDrop, 0, wxTOP|wxLEFT|wxALL|wxEXPAND, 5)
		self.sizer.Add(self.cmbType, 0, wxTOP|wxLEFT|wxALL|wxEXPAND, 5)
		self.sizer.Add(self.lblInst, 0, wxTOP|wxLEFT|wxALL|wxEXPAND, 5)
		self.sizer.Add(self.cmbAssc, 0, wxTOP|wxLEFT|wxALL|wxEXPAND, 5)
		self.sizer.Add(self.btnSize, 1, wxLEFT|wxRIGHT|wxBOTTOM|wxTOP, 5)

		# make it a nice auto layout thingie
		self.SetSizer(self.sizer)
		self.SetAutoLayout(true)
		self.SetSize(self.sizer.GetMinSize())
					
	def OnNameChanged(self, event):
		# change the label
		self.parent.grdPattern.SetColLabelValue(self.whichCol, self.text.GetValue())

	def OnBtnDone(self, event):
		# close the window
		self.Destroy()

	def OnBtnDel(self, event):
		del(envInst[self.whichCol])
		del(trackType[self.whichCol])
		# make sure we delete the last score line too
		del(score[self.whichCol])
		for col in range(self.whichCol, len(envInst)):
			self.parent.grdPattern.SetColLabelValue(col, self.parent.grdPattern.GetColLabelValue(col + 1))
		self.parent.grdPattern.DeleteCols(self.whichCol, 1, FALSE)
		# close the window
		self.Destroy()

	def OnTypeChange(self, event):
		# get our new value
		trackType[self.whichCol] = self.cmbType.GetStringSelection()
		self.parent.updateTrackProperties()
		# make sure we disable the envInst pull down if it's a note type
		if (trackType[self.whichCol] == 'note'):
			self.cmbAssc.Enable(FALSE)
		if (trackType[self.whichCol] == 'envelope'):
			self.cmbAssc.Enable(TRUE)

	def OnAsscChange(self, event):
		# get our new value
		envInst[self.whichCol] = self.cmbAssc.GetStringSelection()

#---------------------------------------------------------------------------
# Class for the primary window
#---------------------------------------------------------------------------
class MyFrame(wxFrame):
	def __init__(self, parent, ID, title):

		# get the last width and height from the config file
		tmpwidth = int(scoConfig.get("general", "width"))
		tmpheight = int(scoConfig.get("general", "height"))
		wxFrame.__init__(self, parent, ID, title, wxDefaultPosition, wxSize(tmpwidth, tmpheight))

		# Create a status bar along the bottom of the screen
		self.CreateStatusBar()
		self.SetStatusText("Sco Tracker")

		# Set up the file menu
		fileMenu = wxMenu()

		# add a bunch of menu items
		fileMenu.Append(ID_MENU_ABOUT, "&About", "More information about this program")
		fileMenu.Append(ID_MENU_OPEN, "&Open", "Open a new project")
		fileMenu.Append(ID_MENU_ADD, "A&dd", "Add a file to current project")
		fileMenu.Append(ID_MENU_SAVE, "&Save", "Save the current project")
		fileMenu.Append(ID_MENU_CLOSE, "&Close", "Close all files")
		fileMenu.AppendSeparator()
		fileMenu.Append(ID_MENU_EXIT, "E&xit", "Terminate the program")

		# Set up the options menu
		#optionsMenu = wxMenu()

		# add an about dialog
		#optionsMenu.Append(ID_MENU_OPTIONS, "Co&nfig", "Configuration")
		#optionsMenu.AppendSeparator()

		# create a new menubar
		menuBar = wxMenuBar()
		
		# add some options to the menu bar
		menuBar.Append(fileMenu, "&File")
		#menuBar.Append(optionsMenu, "O&ptions")
		
		# make it so
		self.SetMenuBar(menuBar)
		
		# EVENTS CODE
		EVT_MENU(self, ID_MENU_ABOUT, self.OnAbout)
		EVT_MENU(self, ID_MENU_EXIT,  self.TimeToQuit)

		# Create a Notebook
		self.nb = wxNotebook(self, ID_NOTEBOOK, style=wxCLIP_CHILDREN)
		EVT_NOTEBOOK_PAGE_CHANGED(self.nb, ID_NOTEBOOK, self.NoteBookChange)
		
		# create each of my major panels
		
		###########################
		# set up the header panel #
		###########################
		self.nb.panelHeader = HeaderPanel(self.nb)
		# wxPanel(self.nb, ID_PANELHEADER, style=wxCLIP_CHILDREN)
		self.nb.AddPage(self.nb.panelHeader, "header")

		# Set up the PATTERNS panel
		self.nb.panelPattern = PatternPanel(self.nb);
		self.nb.AddPage(self.nb.panelPattern, "patterns")

		# Set up the INSTRUMENTS panel (TextCtrl)
		self.nb.panelInstruments = InstrumentPanel(self.nb);
		self.nb.AddPage(self.nb.panelInstruments, "instruments")

		# Set up the output panel
		self.panelOutput = wxTextCtrl(self.nb, -1, style = wxTE_MULTILINE|wxTE_AUTO_SCROLL)
		self.nb.AddPage(self.panelOutput, "Csound output")

		# Set up the csound panel
		self.nb.panelCsound = wxTextCtrl(self.nb, -1, style = wxTE_MULTILINE|wxTE_AUTO_SCROLL)
		self.nb.AddPage(self.nb.panelCsound, "Csound commands")

		# set this panel to be the output
		csIO.setupOutput(self.panelOutput, self)

		# add the tracks we were last working on to our list
		self.nb.panelHeader.loadLast()

		# load in the last tracks we worked on
		self.nb.panelPattern.loadLast()

		# what happens if the user closes this window
		EVT_CLOSE(self, self.OnCloseMe)
		
	##########
	# Events #
	##########

	# when the notebook page is changed
	def NoteBookChange(self, event):
		pass
		# print event.GetSelection()
		

	# menu event ABOUT
	def OnAbout(self, event):
		dlg = wxMessageDialog(self, "Sco Tracker (c) Chris McCormick, 2001.", "About this program", wxOK | wxICON_INFORMATION)
		dlg.ShowModal()
		dlg.Destroy()

	# menu event EXIT
	def TimeToQuit(self, event):
		self.Close(true)
	
	# any other kind of close
	def OnCloseMe(self, event):
		# save all our instruments
		self.nb.panelInstruments.saveInstruments()
		# save all our tracks
		self.nb.panelPattern.writeTracks()
		if scoConfig.options("general"):
			# save the width and height of our last used window
			(tmpwidth, tmpheight) = self.GetSizeTuple()
			scoConfig.set("general", "width", tmpwidth)
			scoConfig.set("general", "height", tmpheight)
		# save the last list of samples we were working on
		self.nb.panelHeader.writeSamples()
		# write out the current configuration
		configfile = open(os.path.expanduser("~/.ScoTracker/config"), 'w')
		scoConfig.write(configfile)
		configfile.close()
		# destroy the window
		self.Destroy()

#---------------------------------------------------------------------------
# Class for the sequencer
#---------------------------------------------------------------------------
class Sequencer:
	def __init__(self, notebook):
		# initialize and setup
		self.currentTick = 0
		# set the notebook object so we can reference it
		self.notebook = notebook

	# start the sequencer
	def StartSequencer(self, speed, where):
		# where did we start from?
		self.start = where
		# set up variables
		self.currentTick = where
		self.speed = speed
		# figure out what we're sending to csound
		self.Play()
		# start the timer
		csIO.sendText("i998 0 -1 " + str(speed) + "\n")

	# timer notification
	def Next(self):
		row = self.currentTick
		# start scrolling
		self.notebook.panelPattern.grdPattern.SelectRow(self.currentTick - self.notebook.panelPattern.spnPattern.GetValue() * self.notebook.panelHeader.spnTicks.GetValue() * self.notebook.panelHeader.spnBeats.GetValue())
		# make sure we can see the current selection
		# self.parent.grdPattern.MakeCellVisible(self.currentTick, 0)
		# increment what tick we're up to in the song
		if (self.currentTick > (self.notebook.panelPattern.spnPattern.GetValue() + 1) * self.notebook.panelHeader.spnTicks.GetValue() * self.notebook.panelHeader.spnBeats.GetValue()):
			# make sure we switch pattern pages each time we reach the end of a pattern
			self.notebook.panelPattern.spnPattern.SetValue(int(self.notebook.panelPattern.spnPattern.GetValue() + 1))
			self.notebook.panelPattern.setPattern(self.notebook.panelPattern.spnPattern.GetValue())
		# if we go past the end of the song
		if (self.currentTick >= len(score[0])):
			self.StopSequencer()
		self.currentTick += 1

	# start the sequence playing
	def Play(self):
		# reset our variables
		noteOut = ""
		# output all of the envelopes to start
		for instNum in instruments.keys():
			noteOut += "i" + instNum + " 0 0.01 5.6 5.6\n"

		for col in range(len(score)):
			# make sure our last note is the last note
			lastnote = len(score[0])
			# last recorded envelope value
			lastenv = "0"
			# start at the end and work backwards
			for row in range(len(score[0]) - 1, self.currentTick-1, -1):
				# if they have selected specific channels, only place those channels
#				if ((self.notebook.panelPattern.grdPattern.IsSelection() and self.notebook.panelPattern.grdPattern.IsInSelection(row, col)) or not self.notebook.panelPattern.grdPattern.IsSelection()):
					# if this is a note event handle it like a note
					if (trackType[col] == 'note'):
						# figure out the note, octave, and instrument number of this event
						note = score[col][row][0:2]
						octave = score[col][row][3:4]
						whichInstrument = score[col][row][6:8]
						# if the event isn't in the list of notes, don't do anything
						if (note != ''):
							if keyinfo.cpsval.has_key(note):
								noteOut += "i" + str(whichInstrument) + " " + str((row - self.start) * (60.0 / (self.speed * 4))) + " " + str((lastnote - row)  * (60.0 / (self.speed * 4))) + " " + octave + "." + keyinfo.cpsval[note] + "\n"
							lastnote = row
					# if our track type is an envelope
					if (trackType[col] == 'envelope'):
						# get the value to be output
						val = score[col][row]
						if (val != ''):
							# read the last character to see if it's a gradient or a flat line
							lastchar = score[col][row][-1:]
							# figure out what instrument we're using
							for instNum in instruments.keys():
								if (envInst[col] == "instrument " + instNum) or (instruments[instNum] == envInst[col]):
									whichInstrument = instNum
							# if it's a gradient
							if (lastchar == '-'):
								# cut off the minus sign
								val = val[:-1]
								noteOut += "i" + whichInstrument + " " + str((row - self.start) * (60.0 / (self.speed * 4))) + " " + str((lastnote - row)  * (60.0 / (self.speed * 4))) + " " + val + " " + lastenv + "\n"
							# else if it's just a constant
							else:
								# output our flat line envelope event
								noteOut += "i" + whichInstrument + " " + str((row - self.start) * (60.0 / (self.speed * 4))) + " " + str((lastnote - row)  * (60.0 / (self.speed * 4))) + " " + val + " " + val + "\n"
							lastenv = val
							lastnote = row
					# if this track is a normal csound score event
					if (trackType[col] == 'score code'):
						val = score[col][row]
						noteOut += val + "\n"
						lastnote = row
		# send data to the Csound instance
		csIO.sendText(noteOut)
		self.notebook.panelCsound.SetValue(noteOut)

	# stop the sequencer
	def StopSequencer(self):
		csIO.stopCS()
		csIO.startCS()		
		self.currentTick = 0

#---------------------------------------------------------------------------
# Class for interfacing the Csound executable
#---------------------------------------------------------------------------
class CsoundIO(wxTimer):
	def __init__(self):
		wxTimer.__init__(self)
		# initialize the outgoing data to be nothing
		self.data = ""
		# start Csound
		self.startCS()

	# set the text output reference
	def setupOutput(self, which, app):
		self.textOutput = which
		self.application = app

	# set up us to use the bomb
	def setupSequencer(self, which):
		self.sequencer = which

	# timer notification
	def Notify(self):
		# if we have some incoming data, then poll the pipe to make sure we can write
		if self.data:
			ofs = [self.pid]
		else:
			ofs = []

		# poll the pipe for reading, writing and errors
		ifd, ofd, efd = select([self.pod, self.ped], ofs, [self.pod, self.ped, self.pid], 0)

		# if there's something to read, get it in
		if self.pod in ifd:
			output = os.read(self.pod, 4096)
			# make sure we don't print lines which are sequencer events
			printme = 1
			# if this is a 'next note' event
			for matches in re.findall('998(.*?)998', output):
				printme = -1
				# if we have a sequencer object send it a 'next' event
				try:
					self.sequencer.Next()
				except:
					pass
			# take out our sequencer events from the 
			output = re.sub("\Wi\W998\Wtime(\W+)(.*?)998.[0]+[\r\n]", "", output)
			try:
				self.textOutput.AppendText(output)
			except:
				pass
								

		# if there's some error, then read it in (most Csound output is done to the error pipe)
		if self.ped in ifd:
			errors = os.read(self.ped, 4096)
			# make sure there IS actually a pipe (otherwise we know csound has died)
			if not errors:
				if not (self.alerted == 1):
					self.alerted = 1
					dlg = wxMessageDialog(self.application, "Csound has died. Check the Output for orchestra errors.\nModifying the orchestra will restart Csound.", "Csound died", wxOK | wxICON_INFORMATION)
					dlg.ShowModal()
					dlg.Destroy()
			else:
				print errors,
				
		# if there's some data to send, and we're allowed to send on this pipe
		if self.data and (self.pid in ofs):
			# print "sending data :"
			# print self.data,
			# send as much as we can
			n = 0
			if (self.alerted == -1):
				n = os.write(self.pid, self.data)
				self.data = self.data[n:]

	# send data to the Csound instance					
	def sendText(self, textOut):
		self.data = self.data + textOut
		# make sure it's updated straight away
		self.Notify()

	def stopCS(self):
		if (self.alerted == -1):
			# make sure it's dead
			self.sendText("e\ne\n");
			# close all our pipes for good housekeeping
			self.stread.close()
			self.stwrite.close()
			self.sterror.close()

	def startCS(self):
		# make sure our 'alert box already popped' is off
		self.alerted = -1
		# open pipes to a new Csound instance
		(self.stwrite, self.stread, self.sterror) = os.popen3("csound -H1 -g -m0 --sched -o devaudio -L stdin ~/.ScoTracker/default.orc")
		# get file descriptors for all pipes
		self.pid = self.stwrite.fileno()
		self.pod = self.stread.fileno()
		self.ped = self.sterror.fileno()
		# if we have a text output reference set up, then use it
		try:
			# clear the output
			self.textOutput.Clear()
		except:
			pass
		# add any samples loaded to Csound
		for fnumber in trackInfo.options("samples"):
			if (os.path.isfile(os.path.expanduser("~/.ScoTracker/working/") + trackInfo.get("samples", fnumber))):
				self.sendText(fnumber + " 0 0 -1 \"" + os.path.expanduser("~/.ScoTracker/working/") + trackInfo.get("samples", fnumber) + "\" 0 0 0\n")
				try:
					self.textOutput.AppendText(fnumber + " 0 0 -1 \"" + os.path.expanduser("~/.ScoTracker/working/") + trackInfo.get("samples", fnumber) + "\" 0 0 0\n")
				except:
					pass
			else:
				# if there's no file corresponding then remove the entry
				trackInfo.remove_option("samples", fnumber)

#---------------------------------------------------------------------------
# Main class for my whole app
#---------------------------------------------------------------------------
class ScoApp(wxApp):
	def OnInit(self):
		frame = MyFrame(NULL, -1, "Sco Tracker")

		# show our main frame
		frame.Show(true)
		self.SetTopWindow(frame)
		return true

#---------------------------------------------------------------------------
# Main 
#---------------------------------------------------------------------------
def main():
	# check if we've got a ScoTracker working directory. If not, create one.
	if (not os.path.exists(os.path.expanduser("~/.ScoTracker"))):
		print "Creating ScoTracker user directory"
		os.mkdir(os.path.expanduser("~/.ScoTracker"))

	# make sure that the current track directory exists
	if (not os.path.exists(os.path.expanduser("~/.ScoTracker/working"))):
		print "Creating ScoTracker working directory"
		os.mkdir(os.path.expanduser("~/.ScoTracker/working"))

	# let's go
	app = ScoApp()
	app.MainLoop()

	# make sure all our sub processes are dead
	csIO.stopCS()

if __name__=='__main__':
	# start Csound executable in the background
	csIO = CsoundIO()

	# run the main
	main()
