<commit_before>#!/usr/bin/env python
#
# GUI Autoplay mode (Jukebox) for Organ Donor Organelle
#
# Tkinter GUI functions used to control MIDO MIDI interface library to play MIDI files
# from the filesystem and provide a fun, obvious user interface. This program runs under
# the GUI supervisor, so it expects to run forever until it receives a SIGUSR1, at which
# point it shuts down immediately and exits.
#
# 			Looks roughly like this:
# 
# 				Now Playing:                   3:56 of 9:21
# 				Title of Currently Playing Song
# 	
# 				^   Up Next:
# 				v   Title of candidate next song        *
#    				  |<     >       ||      >|
#
#
# 2015-10 ptw

from Tkinter import *
import tkFont

import random
import sys
import signal
import re
import time

import os
from os import listdir
from os.path import isfile, join

import mido
from mido import MidiFile, MetaMessage
import rtmidi

SONGS_SUBDIRECTORY = "songs"

deployed_mode = isfile("deployed.txt")		# Create this file to go full-screen, etc.

# Colors for the progress indicator
active_color = "#888"
inactive_color = "#ccc"
paused_color = "#aaa"

# colors for the three main frames of the UI
top_bg = "#eee"
mid_bg = "#8888ff"
bot_bg = "#b8b8b8"


def frac(x):
	"""Return the fractional part of a positive number.
	"""
	return x - int(x)


class TimeProgressLabel(Label):
	"""Text Label-based widget to display progress in the form "m:ss of m:ss"
	
	* Set it up with reset, passing in the total duration of the playback.
	* Update it incrementally with advance each time a known interval has passed.
	The first such update starts time running.
	Finalize it with done so the final time display exactly matches the duration.
	"""
	def __init__(self, parent, *args, **kwargs):
		Label.__init__(self, parent, *args, **kwargs)
		self.elapsed = 0
		self.displayed_seconds = 0
		self.duration = 0
		self.last_realignment = -1
		self.seconds_updater = None
		self._update_string(0.0)
		self.config(fg=inactive_color)
		
	def _update_string(self, t):
		self.config(text = "%d:%02d of %d:%02d" % (int(t/60),int(t%60),int(self.duration/60),int(self.duration%60)))
		print "displaying: %d:%02d of %d:%02d" % (int(t/60),int(t%60),int(self.duration/60),int(self.duration%60)), 
		
	def reset(self, seconds):
		"""Initialize the total duration and reset the elapsed time to zero.
		"""
		self.elapsed = 0
		self.duration = seconds
		if self.seconds_updater != None:
			self.after_cancel(self.seconds_updater)
			self.seconds_updater = None
		self._update_string(0.0)
		self.systime_offset = None
		self.config(fg=inactive_color)
		print "reset progress"
				
	def advance(self, midi_incremental_time):
		"""Advance the elapsed time by a specified amount.
		
		MIDI time is considered authoritative; we don't care about real time at all.
		So the cumulative sum of these advances is the definitive elapsed time for the song.
	
		This routine is responsible for scheduling the display update on integral seconds.
		We keep the timer updated with the latest idea of time from calls to advance().
		We also keep track of the offset between system time and MIDI time, so that the
		display updater can always compute the right time to display, even during long
		notes or rests.
		"""
		self.elapsed += midi_incremental_time
		self.systime_offset = time.time() - self.elapsed
		print "increment: ", midi_incremental_time, " elapsed: ", self.elapsed, "system offset: ", self.systime_offset
		
		# Update the timer to expire on the next second boundary.
		# There's a race here; the timer might be expiring right now.
		# That's OK because the timer handler is designed to display the right time
		# even if is called multiple times.
		# Is it possible for the handler to get called zero times? Maybe, depending on
		# the (undocumented) behavior of self.cancel_after(). If that does happen, the
		# handler would just update by 2 at the next second boundary, so at least the error
		# would be transient.
		if self.seconds_updater is not None:
			self.after_cancel(self.seconds_updater)
		self.seconds_updater = self.after(int(1000*(1.0-frac(self.elapsed))), self._next_second)
	
		self.config(fg=active_color)
		
	def	_next_second(self):
		"""Update the display once per second of elapsed (MIDI) time.
		
		We've tried to arrange for this event to fire very near the second boundary
		(in MIDI time) for every second boundary. Due to an unavoidable race condition,
		it might fire more than once for the same second boundary. Thus it is crucial
		that this function display the correct time without trying to count invocations.
		We do this by keeping track of the offset between system time and elapsed (MIDI)
		time. This function subtracts that offset from system time to get the time
		to display.
		
		This event reschedules itself for 1.0 seconds later, just in case there are
		no advance() events during the coming second. More often, there will be.
		"""	
		self.seconds_updater = self.after(1000, self._next_second)	# just in case
		if self.systime_offset is not None:
			self._update_string(round(time.time() - self.systime_offset))
		
	def pause(self):
		"""Pause timekeeping without disrupting elapsed time.
		After calling pause(), you may just start calling advance() again to resume.
		"""
		if self.seconds_updater is not None:
			self.after_cancel(self.seconds_updater)
			self.seconds_updater = None
		self.systime_offset = None
		self.config(fg=paused_color)
				
	def done(self):
		"""Stop timekeeping at the end of a song.
		"""
		self._update_string(self.duration)		# just to be sure it looks right!
		self.config(fg=inactive_color)
		if self.seconds_updater is not None:
			self.after_cancel(self.seconds_updater)
			self.seconds_updater = None
		self.systime_offset = None
		self.config(fg=inactive_color)

	
def everything_off():
	"""Turn off every note, in case it's stuck playing.
	"""
	for mynote in range(1,128):
		out.send(mido.Message('note_off', note=mynote, velocity=100))<commit_msg>Remove duplicate call to set inactive color<commit_after>#!/usr/bin/env python
#
# GUI Autoplay mode (Jukebox) for Organ Donor Organelle
#
# Tkinter GUI functions used to control MIDO MIDI interface library to play MIDI files
# from the filesystem and provide a fun, obvious user interface. This program runs under
# the GUI supervisor, so it expects to run forever until it receives a SIGUSR1, at which
# point it shuts down immediately and exits.
#
# 			Looks roughly like this:
# 
# 				Now Playing:                   3:56 of 9:21
# 				Title of Currently Playing Song
# 	
# 				^   Up Next:
# 				v   Title of candidate next song        *
#    				  |<     >       ||      >|
#
#
# 2015-10 ptw

from Tkinter import *
import tkFont

import random
import sys
import signal
import re
import time

import os
from os import listdir
from os.path import isfile, join

import mido
from mido import MidiFile, MetaMessage
import rtmidi

SONGS_SUBDIRECTORY = "songs"

deployed_mode = isfile("deployed.txt")		# Create this file to go full-screen, etc.

# Colors for the progress indicator
active_color = "#888"
inactive_color = "#ccc"
paused_color = "#aaa"

# colors for the three main frames of the UI
top_bg = "#eee"
mid_bg = "#8888ff"
bot_bg = "#b8b8b8"


def frac(x):
	"""Return the fractional part of a positive number.
	"""
	return x - int(x)


class TimeProgressLabel(Label):
	"""Text Label-based widget to display progress in the form "m:ss of m:ss"
	
	* Set it up with reset, passing in the total duration of the playback.
	* Update it incrementally with advance each time a known interval has passed.
	The first such update starts time running.
	Finalize it with done so the final time display exactly matches the duration.
	"""
	def __init__(self, parent, *args, **kwargs):
		Label.__init__(self, parent, *args, **kwargs)
		self.elapsed = 0
		self.displayed_seconds = 0
		self.duration = 0
		self.last_realignment = -1
		self.seconds_updater = None
		self._update_string(0.0)
		self.config(fg=inactive_color)
		
	def _update_string(self, t):
		self.config(text = "%d:%02d of %d:%02d" % (int(t/60),int(t%60),int(self.duration/60),int(self.duration%60)))
		print "displaying: %d:%02d of %d:%02d" % (int(t/60),int(t%60),int(self.duration/60),int(self.duration%60)), 
		
	def reset(self, seconds):
		"""Initialize the total duration and reset the elapsed time to zero.
		"""
		self.elapsed = 0
		self.duration = seconds
		if self.seconds_updater != None:
			self.after_cancel(self.seconds_updater)
			self.seconds_updater = None
		self._update_string(0.0)
		self.systime_offset = None
		self.config(fg=inactive_color)
		print "reset progress"
				
	def advance(self, midi_incremental_time):
		"""Advance the elapsed time by a specified amount.
		
		MIDI time is considered authoritative; we don't care about real time at all.
		So the cumulative sum of these advances is the definitive elapsed time for the song.
	
		This routine is responsible for scheduling the display update on integral seconds.
		We keep the timer updated with the latest idea of time from calls to advance().
		We also keep track of the offset between system time and MIDI time, so that the
		display updater can always compute the right time to display, even during long
		notes or rests.
		"""
		self.elapsed += midi_incremental_time
		self.systime_offset = time.time() - self.elapsed
		print "increment: ", midi_incremental_time, " elapsed: ", self.elapsed, "system offset: ", self.systime_offset
		
		# Update the timer to expire on the next second boundary.
		# There's a race here; the timer might be expiring right now.
		# That's OK because the timer handler is designed to display the right time
		# even if is called multiple times.
		# Is it possible for the handler to get called zero times? Maybe, depending on
		# the (undocumented) behavior of self.cancel_after(). If that does happen, the
		# handler would just update by 2 at the next second boundary, so at least the error
		# would be transient.
		if self.seconds_updater is not None:
			self.after_cancel(self.seconds_updater)
		self.seconds_updater = self.after(int(1000*(1.0-frac(self.elapsed))), self._next_second)
	
		self.config(fg=active_color)
		
	def	_next_second(self):
		"""Update the display once per second of elapsed (MIDI) time.
		
		We've tried to arrange for this event to fire very near the second boundary
		(in MIDI time) for every second boundary. Due to an unavoidable race condition,
		it might fire more than once for the same second boundary. Thus it is crucial
		that this function display the correct time without trying to count invocations.
		We do this by keeping track of the offset between system time and elapsed (MIDI)
		time. This function subtracts that offset from system time to get the time
		to display.
		
		This event reschedules itself for 1.0 seconds later, just in case there are
		no advance() events during the coming second. More often, there will be.
		"""	
		self.seconds_updater = self.after(1000, self._next_second)	# just in case
		if self.systime_offset is not None:
			self._update_string(round(time.time() - self.systime_offset))
		
	def pause(self):
		"""Pause timekeeping without disrupting elapsed time.
		After calling pause(), you may just start calling advance() again to resume.
		"""
		if self.seconds_updater is not None:
			self.after_cancel(self.seconds_updater)
			self.seconds_updater = None
		self.systime_offset = None
		self.config(fg=paused_color)
				
	def done(self):
		"""Stop timekeeping at the end of a song.
		"""
		self._update_string(self.duration)		# just to be sure it looks right!
		self.config(fg=inactive_color)
		if self.seconds_updater is not None:
			self.after_cancel(self.seconds_updater)
			self.seconds_updater = None
		self.systime_offset = None

	
def everything_off():
	"""Turn off every note, in case it's stuck playing.
	"""
	for mynote in range(1,128):
		out.send(mido.Message('note_off', note=mynote, velocity=100))
{'fraction_non_alphanumeric': 0.08493495593789341, 'fraction_numerical': 0.012757028955098615, 'mean_word_length': 3.857725234406849, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 7, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4684386', 'n_tokens_mistral': 4084, 'n_tokens_neox': 3663, 'n_words': 1428}