def __init__(self, globalRoot, app): self.globalRoot = globalRoot self.audioInstance = Autoload().getInstanceAudio() self.app = app self.nbOfLoops = 2 self.backtrack = None self.window = tk.Toplevel(self.globalRoot) self.window.config(cursor="none") self.window.attributes('-fullscreen', True) self.window.geometry("320x480") self.window["bg"] = "black" self.currentTrack = self.audioInstance.getCurrentTrack() trackName = self.currentTrack[0].split("/")[-1] trackLength = "{0:.2f}".format(self.currentTrack[1]) self.window.lblStatic1 = MyLabel24(self.window, text="How many bars?") self.window.btnWithBacktrack = BtnBlack12(self.window, text="OK") self.window.btnWithBacktrack.config(command=self.nextWindow) self.window.nbOfLoops = MyLabel40(self.window, text=self.nbOfLoops) self.window.btnLess = BtnBlack12(self.window, text="<", command=self.lessLoops) self.window.btnMore = BtnBlack12(self.window, text=">", command=self.moreLoops) # self.window.lblBacktrack = MyLabel12(self.window, text="length one loop {} sec.".format(str(round(self.currentTrack[1],2)))) self.window.lblBacktrack = MyLabel12(self.window, text="") self.window.btnWithoutBacktrack = BtnBlack12(self.window, text="Cancel", command=self.cancel) self.window.lblStatic2 = MyLabel12( self.window, wraplength=320, text="current backtrack:\n{}\n({} ms)".format( trackName, trackLength)) # PLACEMENT yplacement = 40 self.window.lblStatic1.place(x=0, y=yplacement, width=320, height=50) yplacement += 60 self.window.btnLess.place(x=0, y=yplacement, width=80, height=60) self.window.nbOfLoops.place(x=40, y=yplacement, width=240, height=60) self.window.btnMore.place(x=240, y=yplacement, width=80, height=60) yplacement += 80 self.window.lblStatic2.place(x=0, y=yplacement, width=320, height=80) yplacement += 100 # self.window.lblBacktrack.place(x=20,y=yplacement, width=280, height=60) self.window.btnWithBacktrack.place(x=40, y=yplacement, width=240, height=60) yplacement += 80 self.window.btnWithoutBacktrack.place(x=40, y=yplacement, width=240, height=60)
def __init__(self, parent, config): self.questionArray = [] self.config = config self.delay = float(self.config["question_delay"]) / 100 self.parent = parent self.isListening = False # variable for user score self.counter = 0 self.score = 0 self.globalIsListening = True debug = True self.stopGame = False self.waitingNotes = [] self.midiIO = Autoload().getInstance() # open connections and ports self.midiIO.setCallback(self.handleMIDIInput) # gamestate is used to know when the user is guessing self.gameState = "notStarted" # startGame self.startGame() self.startingNote = -1 self.parent.btnSkip.configure(command=self.skip) self.melodies = Melody(self)
def __init__(self, bpm, backtrack, backtrackDuration, nbOfLoops, callback): self.sound = Autoload().getInstanceAudio() self.sound.loadTick() self.backtrack = backtrack self.backtrackDuration = backtrackDuration self.nbOfLoops = nbOfLoops # self.saved_volume = pygame.mixer.music.get_volume() # print(self.saved_volume) # pygame.mixer.music.set_volume(1) self.bpm = bpm self.count = 4 self.delayBetweenBeats = 60 / float(self.bpm) print("delay between notes", self.delayBetweenBeats) self.callback = callback self.t1 = Timer(self.delayBetweenBeats, lambda: self.playFirstTick()) self.t2 = Timer(2 * self.delayBetweenBeats, lambda: self.playTick()) self.t3 = Timer(3 * self.delayBetweenBeats, lambda: self.playTick()) self.t4 = Timer(4 * self.delayBetweenBeats, lambda: self.playLastTick()) self.t1.start() self.t2.start() self.t3.start() self.t4.start()
def __init__(self, parent, config): self.config = config self.parent = parent # print("config mode 0 ",config) self.delay = float(config["question_delay"]) / 100 self.isListening = False # variable for user score self.counter = 0 self.score = 0 self.globalIsListening = True debug = True self.stopGame = False self.waitingNotes = [] self.initMIDIArray(128) self.midiIO = Autoload().getInstance() self.midiIO.setCallback(self.handleMIDIInput) # gamestate is used to know when the user is guessing self.gameState = "notStarted" # startGame self.startGame() self.startingNote = -1 self.changeAllBg("black") self.parent.btnSkip.configure(command=self.skip) self.maxInterval = 12
def __init__(self, globalRoot, root, config, app): # images self.playImage = ImageTk.PhotoImage(Image.open(env.PLAY_IMAGE)) self.pauseImage = ImageTk.PhotoImage(Image.open(env.PAUSE_IMAGE)) self.shuffleImage = ImageTk.PhotoImage(Image.open(env.SHUFFLE_IMAGE)) self.midiIO = Autoload().getInstance() self.midiIO.setCallback(self.handleMIDIInput) self.isPlaying = False self.config = config self.globalRoot = globalRoot self.root = root self.app = app # Default path self.midiRepository = env.MIDI_FOLDER # Callbacks for buttons # self.root.btnRecord.config(command=self.showWithOrWithoutBacktrackWindow) self.root.btnPractiseLick.config(image=self.playImage, command=self.playOneLick) self.root.btnRandomLick.config(image=self.shuffleImage, command=self.pickRandomLick) self.root.btnDeleteSelected.config(command=self.deleteLick) self.root.btnPrev.config(command=self.previousLick) self.root.btnNext.config(command=self.nextLick) self.midiFiles = [] self.reloadMidiFiles() self.fileIndex = 0 self.recordNotes = None self.midiIO = Autoload().getInstance() self.audioInstance = Autoload().getInstanceAudio() # self.midiIO.setCallback(self.handleMIDIInput) self.bassNote = 0 self.chordQuality = "-" self.transpose = 0 self.activeCustomSignals = [] self.lickRepetitionCounter = 1 self.lickMaxRepetition = self.config["times_each_transpose"] self.playOnlyChord = False self.currentLickIndex = 0 self.currentLick = None self.practiseAllLicks = False self.lastTranspose = 0 self.futureTranspose = 0 self.playBacktrackThread = None self.audioThread = None self.bass = None self.mType = None self.notes = None self.backtrackVolume = None self.loadASample(len(self.midiFiles) - 1)
def __init__( self, globalRoot, parent, config, app, ): self.currentTrack = None self.tracksWav = None # images self.playImage = ImageTk.PhotoImage(Image.open(env.PLAY_IMAGE)) self.pauseImage = ImageTk.PhotoImage(Image.open(env.PAUSE_IMAGE)) self.shuffleImage = ImageTk.PhotoImage(Image.open(env.SHUFFLE_IMAGE)) self.page=0 self.midiIO = Autoload().getInstance() self.midiIO.setCallback(self.handleMIDIInput) self.globalRoot = globalRoot self.app = app self.config = config metroBpmFile = open(env.CONFIG_METRO_BPM, 'r') self.metroBpm=int(metroBpmFile.read()) self.isPlayingMetro=False self.parent = parent self.sound = Autoload().getInstanceAudio() # CLICK LISTENERS self.parent.btnPlay.config(command=self.toggleBacktrack) self.parent.btnMetro.config(command=self.playMetro) self.parent.btnBpmMinus.config(command=self.decreaseMetroTempo) self.parent.btnBpmPlus.config(command=self.increaseMetroTempo) self.parent.btnRandom.config(command=self.playRandom) self.parent.btnSwitchPage.config(command=self.switchPage) for i in range(0,len(self.parent.wav_buttons)): button=self.parent.wav_buttons[i] # we must extract the part with number of tracks to only keep the category button_text=button['text'].split('\n')[0] button.config(command=lambda button_text=button_text:self.pickRandomSampleInCategory(button_text)) # this is a list which regroups all 4 folders (house , jazz ,latin, hiphop) self.tracksWav = self.sound.tracksWav self.parent.btnLick.config( command=self.showWithOrWithoutBacktrackWindow) self.parent.btnRandom.config(text="", image=self.shuffleImage) # recording variables self.recordingBassLick = False self.recordingNotes = False self.recordingCustomChords = False self.recordedNotes = [] self.recordedCustomChords = [] self.damper = [] self.damperActive = False
def __init__(self, parent, config): # get audio instance to be sure the audio is loaded self.audio = Autoload().getInstanceAudio() self.midiIO = Autoload().getInstance() self.config = config self.parent = parent self.parent.btnConfig.config(command=self.openMidiPanel) self.loadInitialSettings() self.parent.btnSaveDefault.config(command=self.saveConfig)
def new_window(self, intMode): self.audioInstance = Autoload().getInstanceAudio() # in order to create the first instance of audio file try: self.master.body.destroy() del self.app except: print("no window to destroy, recreation ...", intMode) print("Creating new window") # recreation of the body frame (middle frame) self.master.body = tk.Frame(self.master, bg="green") self.master.body.place(x=0, y=60, width=320, height=340) try: del self.app print("Should be empty : ", self.app) except: pass if intMode == 0: self.app = Mode0(self.master.body, self.config) # specific to mode0 bc in order to skip all midi notes during another mode self.app.activateListening() elif intMode == 1: self.app = Mode1(self.master.body, self.config) self.app.activateListening() elif intMode == 2: self.app = Mode2(self.master, self.master.body, self.config, self) elif intMode == 3: self.app = Mode3(self.master, self.master.body, self.config, self) elif intMode == 4: self.app = ModeOptions(self.master.body, self.config, self) else: return self.highLightActiveMode(intMode)
def handleMIDIInput(self, msg): # used for the midiListening button if Autoload().getInstance( ).isListening == False: # check if user has midi listen return if self.globalIsListening == False: return # Needed because we still receive signals even if the class is destroyed if self.isListening == False: print("[--] Ignoring queue message...", msg, self.isListening) return print("[-]receiving something", msg, self.isListening) if msg.type == "note_on" and msg.velocity > 10: # we test according to the gamestate if self.gameState == "waitingUserInput": self.changeGameState("listen") self.startingNote = msg.note # pick a random note questionNote = self.pickNewNote(self.startingNote) self.questionNote = QuestionNote(questionNote, self, self.delay) # show the note on the ui self.lblUserShow = noteNameFull(self.startingNote) + "-> " self.parent.lblNote.config(text=self.lblUserShow) elif self.gameState == "waitingUserAnswer": if msg.note == self.startingNote: # we want to ignore the starting note for the user. return self.checkAnswer(msg.note) # we check the answer
def handleMIDIInput(self, msg): if Autoload().getInstance().isListening == False: return if self.globalIsListening == False: return # Needed because we still receive signals even if the class is destroyed if self.isListening == False: print("[--] Ignoring queue message...", msg, self.isListening) return print("[-]receiving something", msg, self.isListening) if msg.type == "note_on" and msg.velocity > 10: # we test according to the gamestate if self.gameState == "waitingUserInput": self.isListening = False # we will reactivate listening after all notes have been played self.startingNote = msg.note self.parent.lblNote["bg"] = "black" self.parent.lblNote.config(font=("Courier", 30, "bold")) self.parent.lblNote["text"] = noteNameFull(self.startingNote) # pick a random chord intervals self.question = self.pickNewChord(self.startingNote) self.questionArray = self.question[1] self.drawAndPlayAnswer() self.allIsCorrect = True # var to know if we make a mistake in the answrs self.isFirstTry = True self.counter = self.counter + 1 self.answerChord = [] # we initialize an answer chord self.answerBools = [] # we initialize some vars and clear the canvas self.answerIndex = 0 self.canvasCounter = 0 elif self.gameState == "waitingUserAnswer": correctNote = self.questionArray[ self.answerIndex] + self.startingNote self.checkAnswer(msg.note, correctNote) self.answerIndex = self.answerIndex + 1
def test_getTracksWavIsAList(self): self.tracks = Autoload().getTracksWav() self.assertIsInstance(self.tracks, list)
def test_getActiveSampleIndexIsAnInt(self): self.activeSampleIndex = Autoload().getActiveSampleIndex() self.assertIsInstance(self.activeSampleIndex, int)
def test_getActiveSampleIsNotAnEmptyString(self): self.activeSample = Autoload().getActiveSample() self.assertIsInstance(self.activeSample, str) self.assertNotEqual(self.activeSample, "")
def test_getAudioInstanceReturnASound(self): self.instance = Autoload().getInstanceAudio() self.assertIsInstance(self.instance, Audio)
def test_creationOfAudioInstanceIsNotNote(self): self.assertIsNone(self.autoload) self.autoload = Autoload().getInstanceAudio() self.assertIsNotNone(self.autoload)
def changeVolume(self, offset ): actualVol = Audio.getVolume() if actualVol + offset <= 0.01: Autoload().getInstanceAudio().setVolume(0) else: Autoload().getInstanceAudio().setVolume(actualVol+offset)
class RecordNotesGui: def __init__(self, globalRoot, choosenBpm, bassNote, chordQuality, backtrack, backtrackDuration, nbOfLoops, chordNotes, app): # images self.recImage = ImageTk.PhotoImage(Image.open(env.RECORD_IMAGE)) self.app = app self.globalRoot = globalRoot self.midiIO = Autoload().getInstance() self.midiIO.setCallback(self.handleMIDIInput) self.audioInstance = Autoload().getInstanceAudio() self.backtrack = backtrack self.backtrackDuration = backtrackDuration self.nbOfLoops = nbOfLoops self.choosenBpm = choosenBpm self.chordQuality = chordQuality self.bassNote = bassNote self.window = tk.Toplevel(self.globalRoot, cursor="none") self.window.attributes("-fullscreen", True) self.window.geometry("320x480") self.window["bg"] = "black" print("backtrack gui:", self.backtrack) self.chordNotes = chordNotes self.melodyNotes = [] self.customSignals = [] self.window.lbl1 = MyLabel12(self.window, text="Please record now...", wraplength=280) self.stringNotes = "" self.window.lbl2 = MyLabel40(self.window, text="{} {}".format( noteName(self.bassNote), self.chordQuality)) self.window.lbl3 = MyLabel24(self.window, text="Notes : " + self.stringNotes, wraplength=280) self.window.lblRec = MyLabel18(self.window, text="") # Buttons self.window.btnCancel = BtnBlack12(self.window, text="Cancel", command=self.cancel) self.window.btnRetry = BtnBlack12(self.window, text="Retry", command=self.retry) self.window.btnSave = BtnBlack12(self.window, text="Save", command=lambda: self.saveMidi()) # self.window.btnSave.config(state="disabled") self.window.canvas = tk.Canvas(self.window, bd=0, highlightthickness=0) self.window.lbl1.place(x=0, y=40, width=320, height=80) self.window.lbl2.place(x=0, y=120, width=320, height=60) self.window.lbl3.place(x=0, y=180, width=320, height=60) self.window.lblRec.place(x=20, y=250, width=280, height=40) self.window.btnCancel.place(x=20, y=310, width=100, height=130) self.window.btnRetry.place(x=120, y=310, width=80, height=130) self.window.btnSave.place(x=200, y=310, width=100, height=130) self.window.canvas.place(x=20, y=290, width=280, height=10) self.isRecording = False self.precountTimer = Bpm(self.choosenBpm, self.backtrack, self.backtrackDuration, self.nbOfLoops, lambda: self.activateRecording()) self.startingTime = 0 self.damper = [] self.damperIsActive = False def cancel(self): self.window.destroy() self.thread.isAlive = False self.isRecording = False pygame.mixer.music.stop() self.app.new_window(2) del self def reset(self): self.cancelThreads() self.melodyNotes = [] self.customSignals = [] self.window.lbl1.config(text="Record Melody after the ticks") self.stringNotes = "" self.window.lblRec.config(image="") self.isRecording = False self.precountTimer = Bpm(self.choosenBpm, self.backtrack, self.backtrackDuration, self.nbOfLoops, lambda: self.activateRecording()) self.startingTime = 0 def retry(self): self.reset() self.window.canvas.delete("all") self.window.canvas.place_forget() self.window.canvas = tk.Canvas(self.window, bd=0, highlightthickness=0) self.window.canvas.place(x=20, y=290, width=280, height=10) def saveMidi(self): # self.parent.createJson(self.bassNote, self.chordQuality, self.backtrack, self.backtrackDuration,self.nbOfLoops) self.cancelThreads() self.saveLickAsJsonFile() # self.cancelRecorgindThreads() self.window.destroy() self.app.new_window(3) # recreation of window 3 self.isRecording = False self.cancelThreads() del self def cancelThreads(self): self.precountTimer.cancel() try: self.thread.isAlive = False except Exception as e: print(e) for signal in self.customSignals: signal.cancel() self.customSignals = [] pygame.mixer.music.stop() # self.saveMidi() def saveLickAsJsonFile(self): volume = pygame.mixer.music.get_volume() obj = { "bass": self.bassNote, "type": self.chordQuality, "backtrack": self.backtrack, "backtrackDuration": self.backtrackDuration, "nbOfLoops": self.nbOfLoops, "chord_notes": self.chordNotes, "notes": self.melodyNotes, "volumeBacktrack": volume, "additional_latency": 0, # Maybe it will be userful later } # creation d'un objet json json_object = json.dumps(obj, indent=4) # sauvegarde json dans un objet # TODO : Make try excerpt now_string = str(int(round(time.time()) * 1000)) outfile = os.path.join(env.MIDI_FOLDER, now_string + ".json") # TODO : increase counter if file exists print("saving : ", outfile, "data :", json_object) with open(outfile, "w+") as outfile: outfile.write(json_object) # print("file saved") # TODO : maku user info for this self.recordedNotes = [] self.startingTime = 0 self.recordingNotes = False self.recordingBassLick = False # self.reloadMidiFiles() # self.currentLickIndex=len(self.midiFiles)-1 # self.currentLick=self.midiFiles[self.currentLickIndex] # self.loadSelectedItem(self.currentLick) # self.recordNotes.destroy() # close windwo # self.currentLickIndex=len(self.midiFiles)-1 # self.currentLick = self.midiFiles[self.currentLickIndex] # self.loadSelectedItem(self.currentLick) def activateRecording(self): # pass self.startingTime = int(round(time.time() * 1000)) # self.playChord(self.bassNote, self.chordQuality) # in order to play the chord when the user recc for note in self.chordNotes: self.customSignals.append( CustomSignal(self, note["type"], note["note"], note["velocity"], note["time"])) self.isRecording = True self.window.lblRec.config(image=self.recImage) self.thread = MyThread("thread-canvas", self.window.canvas, self.audioInstance, self.backtrackDuration * self.nbOfLoops, self) self.thread.start() # self.parent.recordingNotes = True def endRecording(self): # self.parent.recordingNotes=False self.window.lblRec.config(image="") self.window.lbl1.config( text="Click 'Save' to save the lick in the library.") self.window.canvas.delete("all") # self.window.btnSave.config(state="normal") self.window.canvas.place_forget() def destroy(self): self.window.destroy() def getTimeFromStart(self): return int(round(time.time() * 1000)) - self.startingTime def destroy(self): print("destroying last record windows") self.thread.isAlive = False for timer in self.customSignals: try: timer.cancel() except Exception as e: print(e) del self.thread def handleMIDIInput(self, msg): if self.isRecording == True: # if self.startingTime == 0: # it means it is the first played note # self.startingTime = int(round(time.time()*1000)) if msg.type == "control_change": if msg.control == 64 and msg.value > 64: self.damperIsActive = True if msg.control == 64 and msg.value <= 64: self.damperIsActive = False print("release damper", self.damper) for damperedNote in self.damper: mTime = self.getTimeFromStart() dictionnary = { "type": "note_off", "note": damperedNote, "velocity": 127, "time": mTime } self.damper = [] else: if self.damperIsActive == True and msg.type == "note_off": self.damper.append(msg.note) return else: mTime = self.getTimeFromStart() dictionnary = { "type": msg.type, "note": msg.note, "velocity": msg.velocity, "time": mTime } print("adding note ", dictionnary) self.melodyNotes.append(dictionnary)
class Game: def __init__(self, parent, config): self.config = config self.parent = parent # print("config mode 0 ",config) self.delay = float(config["question_delay"]) / 100 self.isListening = False # variable for user score self.counter = 0 self.score = 0 self.globalIsListening = True debug = True self.stopGame = False self.waitingNotes = [] self.initMIDIArray(128) self.midiIO = Autoload().getInstance() self.midiIO.setCallback(self.handleMIDIInput) # gamestate is used to know when the user is guessing self.gameState = "notStarted" # startGame self.startGame() self.startingNote = -1 self.changeAllBg("black") self.parent.btnSkip.configure(command=self.skip) self.maxInterval = 12 def toggleGlobalListen(self): if self.globalIsListening == True: self.globalIsListening = False self.parent.btnListen.configure(text="ListenOFF") else: self.globalIsListening = True self.parent.btnListen.configure(text="ListenON") def skip(self): try: self.parent.label2["text"] = "It was ;-)\n{}".format( formatOutputInterval(self.questionNote.note - self.startingNote)) self.parent.label2["bg"] = "orange" # if we gave the good answer, we want a new note self.changeGameState("waitingUserInput") except: print("impossible to skip question") def startGame(self): self.changeGameState("waitingUserInput") self.melodies = Melody(self) def destroy(self): # print("destroying...") # self.isListening = False # self.midiIO.destroy() # delete everything in midiIO class self.midiIO.setCallback(None) # del self.waitingNotes # delete WantingNotes # del self def changeGameState(self, newstate): if newstate == "notStarted": pass elif newstate == "waitingUserInput": self.parent.label1["text"] = "Pick a starting Note" self.gameState = "waitingUserInput" percentage = int((self.score / self.counter) * 100) if (self.counter != 0) else 0 self.parent.label3["text"] = "{}/{} ({}%)".format( self.score, self.counter, percentage) elif newstate == "listen": # self.parent["bg"] = "orange" # self.changeAllBg("orange") self.parent.label1["text"] = "Listen ..." self.parent.label2["text"] = "" self.parent.label2["bg"] = "black" self.parent.lblNoteUser["text"] = "" self.gameState = "listen" self.isListening = False elif newstate == "waitingUserAnswer": self.isListening = True # self.parent["bg"] = "blue" # self.changeAllBg("blue") self.parent.label1["text"] = "What is your answer ?" self.gameState = "waitingUserAnswer" # init a 128 array of WaitingNote in order to store all the timers def initMIDIArray(self, maxNote): for i in range(maxNote): self.waitingNotes.append(WaitingNote(i, self)) # callback launched when the timer is at 0 def noteOff(self, note): if self.isListening == False: return self.midiIO.sendOut("note_off", note) # prepare the future midi noteOff it is stored in waitingNotes list def prepareNoteOut(self, mNote, offset=0): if self.gameState == "waitingUserInput": self.changeGameState("listen") elif self.gameState == "listen": self.changeGameState("waitingUserAnswer") self.midiIO.sendOut("note_on", mNote) # send note on currentNote = self.waitingNotes[mNote] currentNote.resetTimer(offset) def checkAnswer(self, answer): if answer == self.questionNote.note: self.parent.label2["text"] = "correct ;-)\n{}".format( formatOutputInterval(self.questionNote.note - self.startingNote)) self.parent.label2["bg"] = "green" self.parent.lblNoteUser["text"] = noteNameFull(answer) self.parent.lblNoteUser["fg"] = "green" if self.questionNote.isFirstTry: self.score = self.score + 1 # TODO : is it really the best way to do time.sleep here ? # if we gave the good answer, we want a new note self.changeGameState("waitingUserInput") time.sleep(0.4) self.melodies.playWinMelody() time.sleep(0.4) else: self.parent.label2["text"] = "incorrect\nA: {}".format( formatOutputInterval(answer - self.startingNote)) self.questionNote.isFirstTry = False self.parent.label2["bg"] = "red" self.parent.lblNoteUser["text"] = noteNameFull(answer) self.parent.lblNoteUser["fg"] = "red" # time.sleep(1) # self.melodies.playLooseMelody() # time.sleep(1) # we replay the interval if the user didnt find the correct answer time.sleep(0.4) self.changeGameState("listen") # i want to replay both notes self.replayNote = QuestionNote(self.startingNote, self, 0) # i want to replay both notes self.replayNote = QuestionNote(self.questionNote.note, self, 0 + self.delay) self.midiIO.panic() def pickNewNote(self, startingNote): self.counter = self.counter + 1 # TODO : make the max interval customizable maxInterval = self.maxInterval offset = 0 while offset == 0: # we dont want the same note than the starting note offset = random.randint(-maxInterval, maxInterval) return startingNote + offset def handleQuestionNote(self, mNote): self.prepareNoteOut def changeAllBg(self, newColor): self.parent.label1["bg"] = newColor self.parent.label2["bg"] = newColor self.parent.label3["bg"] = newColor self.parent.lblNote["bg"] = newColor self.parent.lblNote["fg"] = "white" self.parent.label1["fg"] = "white" self.parent.label2["fg"] = "white" self.parent.label3["fg"] = "white" def handleMIDIInput(self, msg): # used for the midiListening button if Autoload().getInstance( ).isListening == False: # check if user has midi listen return if self.globalIsListening == False: return # Needed because we still receive signals even if the class is destroyed if self.isListening == False: print("[--] Ignoring queue message...", msg, self.isListening) return print("[-]receiving something", msg, self.isListening) if msg.type == "note_on" and msg.velocity > 10: # we test according to the gamestate if self.gameState == "waitingUserInput": self.changeGameState("listen") self.startingNote = msg.note # pick a random note questionNote = self.pickNewNote(self.startingNote) self.questionNote = QuestionNote(questionNote, self, self.delay) # show the note on the ui self.lblUserShow = noteNameFull(self.startingNote) + "-> " self.parent.lblNote.config(text=self.lblUserShow) elif self.gameState == "waitingUserAnswer": if msg.note == self.startingNote: # we want to ignore the starting note for the user. return self.checkAnswer(msg.note) # we check the answer
def __init__(self, globalRoot, bassNote, chordQuality, backtrack, backtrackDuration, nbOfLoops, app): # images self.recImage = ImageTk.PhotoImage(Image.open(env.RECORD_IMAGE)) self.app = app self.globalRoot = globalRoot self.midiIO = Autoload().getInstance() self.midiIO.setCallback(self.handleMIDIInput) self.audioInstance = Autoload().getInstanceAudio() self.backtrack = backtrack self.backtrackDuration = backtrackDuration self.nbOfLoops = nbOfLoops self.chordQuality = chordQuality self.bassNote = bassNote self.choosenBpm = 90 self.sound = Autoload().getInstanceAudio() self.window = tk.Toplevel(self.globalRoot, cursor="none") self.window.attributes("-fullscreen", True) self.window.geometry("320x480") self.window["bg"] = "black" self.window.lblMessage = MyLabel12( self.window, text="Wait for the preparation of the backtrack") self.window.lblMessage.config(wraplength=280) self.window.lblBass = MyLabel40(self.window, text="Key") # self.window.btnRetry=BtnBlack12(self.window, text="Retry") self.window.btnCancel = BtnBlack12(self.window, text="Cancel") self.window.btnCancel.config(command=self.cancel) self.window.btnOK = BtnBlack12(self.window, text="OK") self.window.btnOK.config(command=self.nextWindow, state="disabled") self.window.lblRec = MyLabel18(self.window, text="") self.window.lblRec.config(background="black") self.window.canvas = tk.Canvas(self.window, bd=0, highlightthickness=0) # placement self.window.lblMessage.place(x=0, y=20, width=320, height=80) self.window.lblBass.place(x=0, y=120, width=320, height=50) self.window.lblRec.place(x=30, y=190, width=260, height=40) self.window.canvas.place(x=30, y=230, width=260, height=10) self.window.btnCancel.place(x=20, y=280, width=140, height=160) # self.window.btnRetry.place(x=120, y=360, width=80, height=80) self.window.btnOK.place(x=160, y=280, width=140, height=160) self.threads = [] self.window.lblBass.config( text="{} {}".format(noteName(self.bassNote), self.chordQuality)) # we want to launch a thread, it will activate recording after count-in # self.parent.precountTimer = Bpm(self.choosenBpm, self.backtrack, self.backtrackDuration, self.nbOfLoops, lambda: self.activateRecordingChords()) self.sound.prepareBacktrackForRecord( self.backtrack) # load the backtrack file in pygame self.window.lblMessage.config( text="READY !\n Recording will start when you play a note...") self.chordNotes = [] self.isRecording = True self.startingTime = 0 self.damper = [] self.damperIsActive = False
class Game: def __init__(self, globalRoot, root, config, app): # images self.playImage = ImageTk.PhotoImage(Image.open(env.PLAY_IMAGE)) self.pauseImage = ImageTk.PhotoImage(Image.open(env.PAUSE_IMAGE)) self.shuffleImage = ImageTk.PhotoImage(Image.open(env.SHUFFLE_IMAGE)) self.midiIO = Autoload().getInstance() self.midiIO.setCallback(self.handleMIDIInput) self.isPlaying = False self.config = config self.globalRoot = globalRoot self.root = root self.app = app # Default path self.midiRepository = env.MIDI_FOLDER # Callbacks for buttons # self.root.btnRecord.config(command=self.showWithOrWithoutBacktrackWindow) self.root.btnPractiseLick.config(image=self.playImage, command=self.playOneLick) self.root.btnRandomLick.config(image=self.shuffleImage, command=self.pickRandomLick) self.root.btnDeleteSelected.config(command=self.deleteLick) self.root.btnPrev.config(command=self.previousLick) self.root.btnNext.config(command=self.nextLick) self.midiFiles = [] self.reloadMidiFiles() self.fileIndex = 0 self.recordNotes = None self.midiIO = Autoload().getInstance() self.audioInstance = Autoload().getInstanceAudio() # self.midiIO.setCallback(self.handleMIDIInput) self.bassNote = 0 self.chordQuality = "-" self.transpose = 0 self.activeCustomSignals = [] self.lickRepetitionCounter = 1 self.lickMaxRepetition = self.config["times_each_transpose"] self.playOnlyChord = False self.currentLickIndex = 0 self.currentLick = None self.practiseAllLicks = False self.lastTranspose = 0 self.futureTranspose = 0 self.playBacktrackThread = None self.audioThread = None self.bass = None self.mType = None self.notes = None self.backtrackVolume = None self.loadASample(len(self.midiFiles) - 1) # we need the number of licks in order to show the user def loadASample(self, index): nbOfSamples = len(self.midiFiles) if nbOfSamples == 0: self.root.lblNotes.config(text="No lick, record one first !") else: # we load last sample self.currentLickIndex = index self.loadSelectedItem(self.midiFiles[self.currentLickIndex]) self.root.lblMessage.config(text="Lick {} / {} loaded.".format(index + 1, len(self.midiFiles))) self.showUserInfo(0) def reloadMidiFiles(self): counter = 0 midiFiles = [] for filename in os.listdir(self.midiRepository): # get only json files if os.path.splitext(filename)[1] == ".json": mFile = os.path.join(self.midiRepository, filename) midiFiles.append(mFile) counter += 1 self.midiFiles = midiFiles self.midiFiles.sort() print("Reloading all MIDI files...", len(self.midiFiles)) def loadSelectedItem(self, name): self.loadFile(os.path.join(self.midiRepository, name)) def showUserInfo(self, transpose, counter=-1, nbBeforeTranspose=-1): self.userMessage = "" for note in self.notes: # print(note) if note["type"] == "note_on": if len(self.userMessage) > 30: self.userMessage = self.userMessage[:28] + "..." else: self.userMessage += noteName(note["note"] + transpose) + " " self.root.lblKey.config(foreground="white") self.root.lblKey.config(text="{} {}".format(noteName(self.bass + transpose), self.mType)) self.root.lblNotes.config(foreground="white") self.root.lblNotes.config(text=self.userMessage) self.root.lblMessage.config(text="Lick {} / {} loaded.".format(self.currentLickIndex + 1, len(self.midiFiles))) if counter != -1: self.root.lblFollowing.config( text="{} / {} before transpose...".format(str(counter), str(nbBeforeTranspose)), foreground="white" ) else: self.root.lblFollowing.config(text="") def showUserInfoNextTranspose(self, oldTranspose, newTranspose): beforeKey = noteName(self.bass + oldTranspose) afterKey = noteName(self.bass + newTranspose) self.root.lblKey.config(foreground="orange", text="{}=>{}".format(beforeKey, afterKey + self.mType)) self.root.lblNotes.config(foreground="orange", text=formatOutputInterval(newTranspose - oldTranspose)) self.root.lblFollowing.config(foreground="orange", text="Transpose next loop...") def loadFile(self, mFile): try: with open(mFile, "r") as f: datastore = json.load(f) self.currentLick = mFile self.bass = datastore["bass"] self.notes = datastore["notes"] self.mType = datastore["type"] self.backtrackVolume = datastore["volumeBacktrack"] self.showUserInfo(0) except Exception as e: print("problem loading file :", mFile, e) # def showWithOrWithoutBacktrackWindow(self): # self.cancelThreads() # try : # del self.globalRoot.recordWindow # except Exception as e: # print(e) # self.root.destroy() # self.destroy() # self.globalRoot.recordWindow= RecordWithBacktrack(self.globalRoot, self.app) def playChords(self, transpose): # notes_on_timing =[] # notes_on_notes=[] # notes_on_velocity=[] # notes_off_timing=[] # notes_off_notes=[] # for note in self.chord_notes: # if note["type"] == "note_on": # notes_on_timing.append(note["time"]) # notes_on_notes.append(note["note"]+transpose) # notes_on_velocity.append(note["velocity"]) # elif note["type"]== "note_off": # notes_off_timing.append(note["time"]) # notes_off_notes.append(note["note"]+transpose) # print(notes_on_timing) # print(notes_on_notes) # self.playingThread= TestThread(self,notes_on_timing, notes_on_notes, notes_on_velocity, notes_off_timing, notes_off_notes) self.playingThreadChord = TestThread(self, self.chord_notes, transpose) self.playingThreadChord.start() # for note in self.chord_notes: # self.activeCustomSignals.append(CustomSignal( # self, # note["type"], # note["note"] + transpose, # note["velocity"], # note["time"] # )) def playMelody(self, transpose): # for note in self.melodyNotes: # self.activeCustomSignals.append(CustomSignal( # self, # note["type"], # note["note"] + transpose, # note["velocity"], # note["time"] # )) self.playingThreadMelody = TestThread(self, self.melodyNotes, transpose) self.playingThreadMelody.start() def activateAudioThread(self, completeTraining): # self.cancelThreads() with open(self.currentLick, "r") as jsonfile: jsonLick = json.load(jsonfile) self.key = jsonLick["bass"] self.mType = jsonLick["type"] self.melodyNotes = jsonLick["notes"] self.chord_notes = jsonLick["chord_notes"] self.nbOfLoops = jsonLick["nbOfLoops"] self.backtrack = jsonLick["backtrack"] self.duration = jsonLick["backtrackDuration"] * self.nbOfLoops print("Current file has nb of notes :" + str(len(self.melodyNotes) + len(self.chord_notes))) self.audioThread = BackTrackThread(self, self.backtrack, self.nbOfLoops, completeTraining) self.audioThread.start() def previousLick(self): self.cancelThreads() pygame.mixer.music.stop() self.currentLickIndex += -1 if self.currentLickIndex < 0: self.currentLickIndex = len(self.midiFiles) - 1 self.loadFile(self.midiFiles[self.currentLickIndex]) self.showUserInfo(0) def nextLick(self): self.cancelThreads() pygame.mixer.music.stop() self.currentLickIndex += 1 if self.currentLickIndex >= len(self.midiFiles): self.currentLickIndex = 0 self.loadFile(self.midiFiles[self.currentLickIndex]) self.showUserInfo(0) def playOneLick(self, completeTraining=False): if self.isPlaying == False: self.root.btnPractiseLick.config(image=self.pauseImage) self.isPlaying = True self.activateAudioThread(completeTraining) else: self.root.btnPractiseLick.config(image=self.playImage) self.audioThread.isAlive = False # del self.audioThread self.isPlaying = False self.cancelThreads() return def pickRandomLick(self): # load a new random sample self.loadASample(random.randint(0, len(self.midiFiles) - 1)) self.playOneLick(completeTraining=True) def deleteLick(self): self.cancelThreads() pygame.mixer.music.stop() if messagebox.askokcancel("delete ?", message="are you sure you want to delete the current Lick ?") == True: try: print("-->try to delete lick :", self.currentLick) os.remove(self.currentLick) self.reloadMidiFiles() self.currentLickIndex = 0 if len(self.midiFiles) > 0: self.currentLick = self.midiFiles[self.currentLickIndex] self.loadSelectedItem(self.currentLick) else: self.root.lblNotes.config(text="No lick, record one first !") self.root.lblKey.config(text="") self.root.lblFollowing.config(text="") self.root.lblMessage.config(text="") except Exception as e: print("Error trying to delete lick", self.currentLick, e) self.reloadMidiFiles() def cancelThreads(self): try: self.root.btnPractiseLick.config(text="Practise Lick") except: print("can't update screen") self.isPlaying = False try: self.audioThread.isAlive = False except Exception as e: print("no threads to cancel", e) # we try to kill all notes no already played try: self.playingThreadChord.isAlive = False except Exception as e: print("no threads to cancel", e) try: self.playingThreadMelody.isAlive = False except Exception as e: print("no threads to cancel", e) for signal in self.activeCustomSignals: signal.timer.cancel() del self.activeCustomSignals self.activeCustomSignals = [] self.midiIO.panic() def destroy(self): self.cancelThreads() pygame.mixer.music.stop() try: del self.activeCustomSignals del self.precountTimer except Exception as e: print("error in destroy :", e) del self def handleMIDIInput(self, msg): pass
def toggleMidiListen(self): instance = Autoload().getInstance() instance.panic() instance.toggleListening()
class Game: def __init__( self, globalRoot, parent, config, app, ): self.currentTrack = None self.tracksWav = None # images self.playImage = ImageTk.PhotoImage(Image.open(env.PLAY_IMAGE)) self.pauseImage = ImageTk.PhotoImage(Image.open(env.PAUSE_IMAGE)) self.shuffleImage = ImageTk.PhotoImage(Image.open(env.SHUFFLE_IMAGE)) self.page=0 self.midiIO = Autoload().getInstance() self.midiIO.setCallback(self.handleMIDIInput) self.globalRoot = globalRoot self.app = app self.config = config metroBpmFile = open(env.CONFIG_METRO_BPM, 'r') self.metroBpm=int(metroBpmFile.read()) self.isPlayingMetro=False self.parent = parent self.sound = Autoload().getInstanceAudio() # CLICK LISTENERS self.parent.btnPlay.config(command=self.toggleBacktrack) self.parent.btnMetro.config(command=self.playMetro) self.parent.btnBpmMinus.config(command=self.decreaseMetroTempo) self.parent.btnBpmPlus.config(command=self.increaseMetroTempo) self.parent.btnRandom.config(command=self.playRandom) self.parent.btnSwitchPage.config(command=self.switchPage) for i in range(0,len(self.parent.wav_buttons)): button=self.parent.wav_buttons[i] # we must extract the part with number of tracks to only keep the category button_text=button['text'].split('\n')[0] button.config(command=lambda button_text=button_text:self.pickRandomSampleInCategory(button_text)) # this is a list which regroups all 4 folders (house , jazz ,latin, hiphop) self.tracksWav = self.sound.tracksWav self.parent.btnLick.config( command=self.showWithOrWithoutBacktrackWindow) self.parent.btnRandom.config(text="", image=self.shuffleImage) # recording variables self.recordingBassLick = False self.recordingNotes = False self.recordingCustomChords = False self.recordedNotes = [] self.recordedCustomChords = [] self.damper = [] self.damperActive = False # def destroy(self): # self.sound.unloadAudio() # del self.sound # del self def switchPage(self,): self.page += 1 nb_pages_theorical = int((len(self.parent.wav_buttons) - 0.1) / 3)+1 # this is the theorical number of pages (3 elements by page) print('theorical pages : ', nb_pages_theorical) for button in self.parent.wav_buttons: button.place(x=-200, y=0) if self.page >= nb_pages_theorical: self.page=0 if self.page ==0: self.parent.btnPlay.place(x=-200) self.parent.btnLick.place(x=-200) self.parent.btnMetro.place(x=10, y=20) self.parent.btnBpmMinus.place(x=130, y=20) self.parent.btnBpmPlus.place(x=220, y=20) counter= 0 start_index=0 for button in self.parent.wav_buttons: if (start_index + counter) < len(self.parent.wav_buttons): if counter ==0 : button.place(x=10, y=120) elif counter == 1: button.place(x=170, y=120) elif counter==2: button.place(x=10, y=220) counter+=1 elif self.page >=1: self.parent.btnMetro.place(x=-200) self.parent.btnBpmMinus.place(x=-200) self.parent.btnBpmPlus.place(x=-200) self.parent.btnPlay.place(x=10,y=20, width=140, height=80) self.parent.btnLick.place(x=170,y=20, width=140, height=80) counter= 0 start_index=self.page*3 for i in range(0,3): if (start_index + counter) < len(self.parent.wav_buttons): if counter ==0 : self.parent.wav_buttons[start_index+counter].place(x=10, y=120) elif counter == 1: self.parent.wav_buttons[start_index+counter].place(x=170, y=120) elif counter==2: self.parent.wav_buttons[start_index+counter].place(x=10, y=220) counter+=1 def playMetro(self,): if self.isPlayingMetro==True: self.sound.stopPlay() self.parent.btnMetro.config(text="Metro") self.isPlayingMetro =False else : self.sound.playRealMetro(self.metroBpm) self.parent.btnMetro.config(text="("+str(self.metroBpm)+")") self.isPlayingMetro=True # we would like to generate 2 bars of audio file to certain bpm def decreaseMetroTempo(self,): minBpm=10 self.metroBpm -= 10 if self.metroBpm <=minBpm: # 10 is the minimum tempo self.metroBpm =minBpm self.saveMetroBpm(self.metroBpm) self.isPlayingMetro=False self.playMetro() def increaseMetroTempo(self,): maxBpm=190 self.metroBpm +=10 if self.metroBpm >= maxBpm: # max tempo self.metroBpm =maxBpm self.saveMetroBpm(self.metroBpm) self.isPlayingMetro=False self.playMetro() def saveMetroBpm(self,bpm): metroBpmFile = open(env.CONFIG_METRO_BPM, 'w') metroBpmFile.write(str(bpm)) metroBpmFile.close() def showWithOrWithoutBacktrackWindow(self,): self.cancelThreads() try: del self.parent.recordWindow except Exception as e: print(e) self.parent.destroy() self.destroy() self.globalRoot.recordWindow = RecordWithBacktrack( self.globalRoot, self.app,) def showRandomTracks(self,): self.showCurrentPlayingInLabel() def showCurrentPlayingInLabel(self,): name = os.path.basename(self.currentTrack) # self.parent.labelCurrent.config(text="Currently Playing:\n"+ name + "\n" + str(self.sound.getCurrentTrack()[0]) +" sec") def changeTrack(self, index): try: self.stopBacktrack() except Exception: print("cant stop track during changeTrack.") self.currentTrack = self.activeSample[index] self.playBacktrack() self.showCurrentPlayingInLabel() self.sound.isPlaying = True def pickRandomSampleInCategory(self, category): print(category) self.sound.pickRandomSample(category) self.playBacktrack() def playRandom(self): self.sound.pickRandomSample() # TODO should display somewhere the number of files in the category and the index self.showRandomTracks() if self.sound.isPlaying == True: self.sound.stopPlay() self.currentTrack = self.sound.activeSample[0] self.showCurrentPlayingInLabel() self.sound.isPlaying = False self.playBacktrack() self.sound.isPlaying = True def toggleBacktrack(self): if pygame.mixer.music.get_busy() == True: self.stopBacktrack() self.sound.isPlaying = False else: self.playBacktrack() self.sound.isPlaying = True def stopBacktrack(self): self.sound.stopPlay() self.parent.btnPlay.config(image=self.playImage) def playBacktrack(self): self.sound.simplePlay() self.sound.isPlaying = True self.parent.btnPlay.config(image=self.pauseImage) trackInfo = self.sound.getCurrentTrack() trackName = trackInfo[0].split("/")[-1] trackLength = "{0:.2f}".format(trackInfo[1]) # self.parent.labelCurrent.config( # text="Currently Playing ({} / {}):\n{}\n({} sec length)".format( # self.sound.activeSample[1], str( # len(self.sound.tracksWav)), trackName, str(trackLength), # ) # ) # thread for canvas self.parent.canvas.delete("all") try: self.thread.isAlive = False # kill previous thread except Exception as e: print(e) self.thread = MyThreadForBacktrackScreen( "thread-canvas", self.parent.canvas, self.sound, self.sound.currentFileLength, self,) self.thread.start() def cancelThreads(self): try: self.thread.isAlive = False except Exception as e: print(e) try: del self.thread except Exception as e: print(e) pygame.mixer.music.stop() def destroy(self): print("trying cancel thread") self.cancelThreads() def handleMIDIInput(self, msg): print(msg) # if msg.type == "control_change": # print("pedal ... : ", msg.control, msg.value) # if msg.control == 64 and msg.value > 64: # self.damperActive = True # if msg.control == 64 and msg.value <= 64: # self.damperActive = False # print(self.damper) # self.damper = [] # if self.damperActive == True and msg.type == "note_off": # self.damper.append(msg.note) # # No handle because we want to ignore the midi input messages pass
class RecordChordsGui: def __init__(self, globalRoot, bassNote, chordQuality, backtrack, backtrackDuration, nbOfLoops, app): # images self.recImage = ImageTk.PhotoImage(Image.open(env.RECORD_IMAGE)) self.app = app self.globalRoot = globalRoot self.midiIO = Autoload().getInstance() self.midiIO.setCallback(self.handleMIDIInput) self.audioInstance = Autoload().getInstanceAudio() self.backtrack = backtrack self.backtrackDuration = backtrackDuration self.nbOfLoops = nbOfLoops self.chordQuality = chordQuality self.bassNote = bassNote self.choosenBpm = 90 self.sound = Autoload().getInstanceAudio() self.window = tk.Toplevel(self.globalRoot, cursor="none") self.window.attributes("-fullscreen", True) self.window.geometry("320x480") self.window["bg"] = "black" self.window.lblMessage = MyLabel12( self.window, text="Wait for the preparation of the backtrack") self.window.lblMessage.config(wraplength=280) self.window.lblBass = MyLabel40(self.window, text="Key") # self.window.btnRetry=BtnBlack12(self.window, text="Retry") self.window.btnCancel = BtnBlack12(self.window, text="Cancel") self.window.btnCancel.config(command=self.cancel) self.window.btnOK = BtnBlack12(self.window, text="OK") self.window.btnOK.config(command=self.nextWindow, state="disabled") self.window.lblRec = MyLabel18(self.window, text="") self.window.lblRec.config(background="black") self.window.canvas = tk.Canvas(self.window, bd=0, highlightthickness=0) # placement self.window.lblMessage.place(x=0, y=20, width=320, height=80) self.window.lblBass.place(x=0, y=120, width=320, height=50) self.window.lblRec.place(x=30, y=190, width=260, height=40) self.window.canvas.place(x=30, y=230, width=260, height=10) self.window.btnCancel.place(x=20, y=280, width=140, height=160) # self.window.btnRetry.place(x=120, y=360, width=80, height=80) self.window.btnOK.place(x=160, y=280, width=140, height=160) self.threads = [] self.window.lblBass.config( text="{} {}".format(noteName(self.bassNote), self.chordQuality)) # we want to launch a thread, it will activate recording after count-in # self.parent.precountTimer = Bpm(self.choosenBpm, self.backtrack, self.backtrackDuration, self.nbOfLoops, lambda: self.activateRecordingChords()) self.sound.prepareBacktrackForRecord( self.backtrack) # load the backtrack file in pygame self.window.lblMessage.config( text="READY !\n Recording will start when you play a note...") self.chordNotes = [] self.isRecording = True self.startingTime = 0 self.damper = [] self.damperIsActive = False def nextWindow(self): self.window.destroy() self.globalRoot.recordWindow = RecordNotesGui( self.globalRoot, self.choosenBpm, self.bassNote, self.chordQuality, self.backtrack, self.backtrackDuration, self.nbOfLoops, self.chordNotes, self.app, ) self.destroy() def customChordRetry(self): self.startingTime = 0 self.recordedCustomChords = [] def customChordCancel(self): self.recordingCustomChords = False self.window.destroy() def activateRecordingChords(self): # self.parent.startingTime=0 # in order to start at the first note # self.window.lblRec.config(text="REC.", background="red", foreground="white") self.window.lblRec.config(image=self.recImage) print("nbOfLoops :", self.nbOfLoops) pygame.mixer.music.stop() self.sound.playBacktrackForRecord(self.nbOfLoops) self.thread = MyThread("thread-canvas", self.window.canvas, self.audioInstance, self.backtrackDuration * self.nbOfLoops, self) self.thread.start() def endRecording(self): # self.parent.recordingCustomChords=False self.isRecording = False self.window.lblRec.config(image="", text="Finished!", background="black", foreground="white") self.window.lblMessage.config(text="Press 'OK' to proceed") self.window.btnOK.config(state="normal") self.window.canvas.delete("all") self.window.canvas.place_forget() def cancel(self): self.window.destroy() try: self.thread.isAlive = False except Exception as e: print(e) pygame.mixer.music.stop() self.app.new_window(2) del self # self.parent.cancelThreads() # self.parent.record def getTimeFromStart(self): return int(round(time.time() * 1000)) - self.startingTime def destroy(self): self.thread.isAlive = False del self.thread del self pass def handleMIDIInput(self, msg): if self.isRecording == True: if self.startingTime == 0: print("should start recording") self.activateRecordingChords() self.startingTime = int(round(time.time() * 1000)) # print("first starting TIme trigger", self.startingTime) if msg.type == "control_change": if msg.control == 64 and msg.value > 64: self.damperIsActive = True if msg.control == 64 and msg.value <= 64: self.damperIsActive = False print("release damper", self.damper) for damperedNote in self.damper: mTime = self.getTimeFromStart() dictionnary = { "type": "note_off", "note": damperedNote, "velocity": 127, "time": mTime } self.damper = [] else: if self.damperIsActive == True and msg.type == "note_off": self.damper.append(msg.note) return else: mTime = self.getTimeFromStart() dictionnary = { "type": msg.type, "note": msg.note, "velocity": msg.velocity, "time": mTime } self.chordNotes.append(dictionnary)
class Game: def __init__(self, parent, config): self.questionArray = [] self.config = config self.delay = float(self.config["question_delay"]) / 100 self.parent = parent self.isListening = False # variable for user score self.counter = 0 self.score = 0 self.globalIsListening = True debug = True self.stopGame = False self.waitingNotes = [] self.midiIO = Autoload().getInstance() # open connections and ports self.midiIO.setCallback(self.handleMIDIInput) # gamestate is used to know when the user is guessing self.gameState = "notStarted" # startGame self.startGame() self.startingNote = -1 self.parent.btnSkip.configure(command=self.skip) self.melodies = Melody(self) def toggleGlobalListen(self): if self.globalIsListening == True: self.globalIsListening = False self.parent.btnListen.configure(text="ListenOFF") else: self.globalIsListening = True self.parent.btnListen.configure(text="ListenON") def skip(self): try: notesAnswer = "" for note in self.questionArray: notesAnswer += noteName(note) + "-" self.parent.lblNote.config(font=("Courier", 18, "bold")) self.parent.lblNote["text"] = "It was\n{}".format(notesAnswer) # .format(formatOutputInterval(self.questionNote.note - self.startingNote)) self.parent.lblNote["bg"] = "orange" self.changeGameState( "waitingUserInput" ) # if we gave the good answer, we want a new note except Exception as e: print("Impossible to skip question", e) def startGame(self): self.changeGameState("waitingUserInput") self.parent["bg"] = "black" self.changeAllBg("black") def destroy(self): self.isListening = False self.midiIO.destroy() # delete everything in midiIO class del self.waitingNotes # delete WantingNotes del self def changeGameState(self, newstate): if newstate == "notStarted": pass elif newstate == "waitingUserInput": self.parent.label1["text"] = "Pick a starting Note" self.gameState = "waitingUserInput" percentage = int((self.score / self.counter) * 100) if (self.counter != 0) else 0 self.parent.label3["text"] = "{}/{} ({}%)".format( self.score, self.counter, percentage) elif newstate == "listen": self.parent.label2["bg"] = "black" self.parent.label1["text"] = "Listen ..." self.parent.label2["text"] = "" self.gameState = "listen" self.isListening = False elif newstate == "waitingUserAnswer": self.isListening = True self.parent.label1["text"] = "Your turn !" self.gameState = "waitingUserAnswer" else: print("ERROR : new state not defined", newstate) # callback launched when the timer is at 0 def noteOff(self, note): if self.isListening == False: return self.midiIO.sendOut("note_off", note) # prepare the future midi noteOff it is stored in waitingNotes list def prepareNoteOut(self, mNote, offset=0): if self.gameState == "waitingUserInput": self.changeGameState("listen") elif self.gameState == "listen": if self.canvasCounter == len(self.questionChord) - 1: print("changing state...") self.changeGameState("waitingUserAnswer") self.midiIO.sendOut("note_on", mNote) # send note on currentNote = self.waitingNotes[mNote] currentNote.resetTimer(offset) # draw on the canvas little rectangle to symbolyze the node rw = 2 * self.calcRectWidthOnCanvas() # rw est 2 largeur de rectgangle self.parent.canvas.create_rectangle(rw + self.canvasCounter * rw, 40, rw + self.canvasCounter * rw + rw / 2, 80, fill="white") self.canvasCounter = self.canvasCounter + 1 def drawAndPlayAnswer(self): self.parent.canvas.delete("all") self.timers = [] counter = 0 for note in self.questionArray: print(note + self.startingNote) # self.timers.append(Timer(1, lambda: self.midiIO.sendOut("note_on", self.startingNote+note, 100))) isLast = False if counter == len(self.questionArray) - 1: isLast = True # minimum delay ? self.timers.append( Timer( 1 + counter * self.delay, self.sendOut, ["note_on", self.startingNote + note, 100, isLast], ).start()) self.timers.append( Timer(3 + counter * self.delay, self.sendOut, ["note_off", self.startingNote + note, 100]).start()) counter += 1 def sendOut(self, mType, note, velocity, isLast=False): if mType == "note_on": self.midiIO.sendOut("note_on", note) rw = 2 * self.calcRectWidthOnCanvas( ) # rw est 2 largeur de rectgangle self.parent.canvas.create_rectangle(rw + self.canvasCounter * rw, 40, rw + self.canvasCounter * rw + rw / 2, 80, fill="white") self.canvasCounter = self.canvasCounter + 1 if isLast == True: self.changeGameState("waitingUserAnswer") elif mType == "note_off": self.midiIO.sendOut("note_off", note) def resetQuestion(self): self.parent.canvas.delete("all") self.changeGameState("listen") self.parent.label2["text"] = "incorrect" self.parent.label2["bg"] = "red" self.drawAndPlayAnswer() self.answerBools = [] self.allIsCorrect = True self.parent.canvas.delete("all") self.answerIndex = -1 self.canvasCounter = 0 self.isFirstTry = False self.midiIO.panic( ) # TODO : there is still a bug with first note ringing # affichage score self.parent.canvas.delete("all") def checkAnswer(self, userAnswer, correctAnswer): # print(answer, self.questionNote.note) rw = 2 * self.calcRectWidthOnCanvas() # rw est 2 largeur de rectgangle if userAnswer == correctAnswer: # ok one answer is self.answerBools.append(True) self.parent.canvas.create_rectangle(rw + self.answerIndex * rw, 0, rw + self.answerIndex * rw + rw / 2, 40, fill="green") else: # we made a mistake on one note time.sleep(0.2) self.parent.label2["text"] = "incorrect" self.parent.label2["bg"] = "red" time.sleep(0.4) self.answerBools.append(False) self.allIsCorrect = False self.parent.canvas.create_rectangle(rw + self.answerIndex * rw, 0, rw + self.answerIndex * rw + rw / 2, 40, fill="red") self.resetQuestion() if self.answerIndex == len(self.questionArray) - 1: self.isListening = False if self.allIsCorrect == False: pass # self.changeGameState( "listen") # # # for note in self.questionChord: # print("resetting a timer") # note.resetTimer() # self.answerBools = [] # self.allIsCorrect= True # self.parent.canvas.delete("all") # self.answerIndex=-1 # self.canvasCounter = 0 # self.isFirstTry= False # else: # We won self.parent.lblNote.config(font=("Courier", 18, "bold")) self.parent.lblNote["text"] = "correct ;-)\n{}".format( self.chordName) self.parent.lblNote["bg"] = "green" if self.isFirstTry: self.score = self.score + 1 self.changeGameState("waitingUserInput") time.sleep(1) self.melodies.playWinMelody() time.sleep(1) self.isListening = True self.midiIO.panic( ) # TODO : there is still a bug with first note ringing self.parent self.parent.canvas.delete("all") def pickNewChord(self, startingNote): # TODO : pick random intervals chordsInit = MidiChords() chord = chordsInit.pickRandom() self.chordName = chord[0] self.chordNotes = chord[1] return chord def changeAllBg(self, newColor): self.parent.label1["bg"] = newColor self.parent.label2["bg"] = newColor self.parent.label3["bg"] = newColor self.parent.label1["fg"] = "white" self.parent.label2["fg"] = "white" self.parent.label3["fg"] = "white" def calcRectWidthOnCanvas(self): canvasW = 200 nbRects = len(self.questionArray) # calcul savant ! w = 2 * nbRects + 3 return canvasW / w def handleMIDIInput(self, msg): if Autoload().getInstance().isListening == False: return if self.globalIsListening == False: return # Needed because we still receive signals even if the class is destroyed if self.isListening == False: print("[--] Ignoring queue message...", msg, self.isListening) return print("[-]receiving something", msg, self.isListening) if msg.type == "note_on" and msg.velocity > 10: # we test according to the gamestate if self.gameState == "waitingUserInput": self.isListening = False # we will reactivate listening after all notes have been played self.startingNote = msg.note self.parent.lblNote["bg"] = "black" self.parent.lblNote.config(font=("Courier", 30, "bold")) self.parent.lblNote["text"] = noteNameFull(self.startingNote) # pick a random chord intervals self.question = self.pickNewChord(self.startingNote) self.questionArray = self.question[1] self.drawAndPlayAnswer() self.allIsCorrect = True # var to know if we make a mistake in the answrs self.isFirstTry = True self.counter = self.counter + 1 self.answerChord = [] # we initialize an answer chord self.answerBools = [] # we initialize some vars and clear the canvas self.answerIndex = 0 self.canvasCounter = 0 elif self.gameState == "waitingUserAnswer": correctNote = self.questionArray[ self.answerIndex] + self.startingNote self.checkAnswer(msg.note, correctNote) self.answerIndex = self.answerIndex + 1
def test_creationOfMidiInstanceIsNotNone(self): self.assertIsNone(self.autoload) self.autoload = Autoload().getInstance() self.assertIsNotNone(self.autoload)
class Bpm: def __init__(self, bpm, backtrack, backtrackDuration, nbOfLoops, callback): self.sound = Autoload().getInstanceAudio() self.sound.loadTick() self.backtrack = backtrack self.backtrackDuration = backtrackDuration self.nbOfLoops = nbOfLoops # self.saved_volume = pygame.mixer.music.get_volume() # print(self.saved_volume) # pygame.mixer.music.set_volume(1) self.bpm = bpm self.count = 4 self.delayBetweenBeats = 60 / float(self.bpm) print("delay between notes", self.delayBetweenBeats) self.callback = callback self.t1 = Timer(self.delayBetweenBeats, lambda: self.playFirstTick()) self.t2 = Timer(2 * self.delayBetweenBeats, lambda: self.playTick()) self.t3 = Timer(3 * self.delayBetweenBeats, lambda: self.playTick()) self.t4 = Timer(4 * self.delayBetweenBeats, lambda: self.playLastTick()) self.t1.start() self.t2.start() self.t3.start() self.t4.start() def playFirstTick(self): print("First TICK") self.sound.playTick() def playTick(self): print("TICK") self.sound.playTick() def playLastTick(self): print("TACK - recording...") # self.sound.playTick() self.sound.prepareBacktrackForRecord(self.backtrack) self.sound.playBacktrackForRecord(self.nbOfLoops) self.callback() # pygame.mixer.music.set_volume(self.saved_volume) def cancel(self): try: self.t4.cancel() except Exception as e: print(e) try: self.t3.cancel() except Exception as e: print(e) try: self.t2.cancel() except Exception as e: print(e) try: self.t1.cancel() except Exception as e: print(e)
def openMidiPanel(self): print("opening settings") self.window = tk.Toplevel(self.parent, cursor="none") self.window.attributes("-topmost", True) self.window.config(background="black") self.window.geometry("320x480") yplacement = 10 label = LblSettings(self.window, text="click on your midi IN device:") label.place(x=0, y=yplacement, width=320, height=30) self.labelCurrentIn = LblSettings(self.window, text="actual In is : " + self.midiIO.inport.name) yplacement += 30 self.labelCurrentIn.place(x=0, y=yplacement, width=320, height=30) # get all availables interface self.midiInstance = Autoload().getInstance() # midiInstance.showIOPorts() # just to shoe in the console midi_inputs = self.midiInstance.getAllMidiInputs() midi_outputs = self.midiInstance.getAllMidiOutputs() print(midi_inputs, midi_outputs) yplacement += 40 for mInput in midi_inputs: if not "RtMidi" in mInput: btn = BtnBlack8(self.window, text=mInput) btn.config(command=lambda mInput=mInput: self.setIn(mInput), wraplength=300) btn.place(x=10, y=yplacement, width=300, height=30) yplacement += 30 yplacement += 15 label2 = LblSettings(self.window, text="click on your midi OUT device:") label2.place(x=0, y=yplacement, width=320, height=30) self.labelCurrentOut = LblSettings(self.window, text="actual out is : " + self.midiIO.outport.name) yplacement += 30 self.labelCurrentOut.place(x=0, y=yplacement, width=320, height=30) yplacement += 10 for mOutput in midi_outputs: if not "RtMidi" in mOutput: btn = BtnBlack8(self.window, text=mOutput) btn.config( command=lambda mOutput=mOutput: self.setOut(mOutput), wraplength=300) yplacement += 30 btn.place(x=0, y=yplacement, width=320, height=30) yplacement += 15 btnClose = BtnBlack20(self.window, text="Close", command=self.quitConfig) btnClose.place(x=80, y=380, width=160, height=80)
class Game: def __init__(self, parent, config): # get audio instance to be sure the audio is loaded self.audio = Autoload().getInstanceAudio() self.midiIO = Autoload().getInstance() self.config = config self.parent = parent self.parent.btnConfig.config(command=self.openMidiPanel) self.loadInitialSettings() self.parent.btnSaveDefault.config(command=self.saveConfig) def saveConfig(self): # we must get all the parameters used in the app default_mode = 0 # for this time, default mode is hardcoded question_delay = self.parent.slider1_1.get() difficulty = self.parent.slider1_2.get() times_each_transpose = self.parent.slider2_1.get() nb_of_transpose_before_change = self.parent.slider2_2.get() midi_in = self.midiIO.inport.name midi_out = self.midiIO.outport.name print("settings: ", question_delay, difficulty, midi_in) obj = { "default_mode": default_mode, "question_delay": question_delay, "difficulty": difficulty, "times_each_transpose": times_each_transpose, "nb_of_transpose_before_change": nb_of_transpose_before_change, "MIDI_interface_in": midi_in, "MIDI_interface_out": midi_out, "midi_hotkey": 50, } json_object = json.dumps(obj, indent=4) print("trying to save...", json_object) outfile = env.CONFIG_FILE try: with open(outfile, "w+") as outfile: outfile.write(json_object) messagebox.showinfo(title="save as default", message="Config saved successfully") print("Saved...") except Exception as e: messagebox.showerror(title="save as default", message="Failed at save config") print(e) def openMidiPanel(self): print("opening settings") self.window = tk.Toplevel(self.parent, cursor="none") self.window.attributes("-topmost", True) self.window.config(background="black") self.window.geometry("320x480") yplacement = 10 label = LblSettings(self.window, text="click on your midi IN device:") label.place(x=0, y=yplacement, width=320, height=30) self.labelCurrentIn = LblSettings(self.window, text="actual In is : " + self.midiIO.inport.name) yplacement += 30 self.labelCurrentIn.place(x=0, y=yplacement, width=320, height=30) # get all availables interface self.midiInstance = Autoload().getInstance() # midiInstance.showIOPorts() # just to shoe in the console midi_inputs = self.midiInstance.getAllMidiInputs() midi_outputs = self.midiInstance.getAllMidiOutputs() print(midi_inputs, midi_outputs) yplacement += 40 for mInput in midi_inputs: if not "RtMidi" in mInput: btn = BtnBlack8(self.window, text=mInput) btn.config(command=lambda mInput=mInput: self.setIn(mInput), wraplength=300) btn.place(x=10, y=yplacement, width=300, height=30) yplacement += 30 yplacement += 15 label2 = LblSettings(self.window, text="click on your midi OUT device:") label2.place(x=0, y=yplacement, width=320, height=30) self.labelCurrentOut = LblSettings(self.window, text="actual out is : " + self.midiIO.outport.name) yplacement += 30 self.labelCurrentOut.place(x=0, y=yplacement, width=320, height=30) yplacement += 10 for mOutput in midi_outputs: if not "RtMidi" in mOutput: btn = BtnBlack8(self.window, text=mOutput) btn.config( command=lambda mOutput=mOutput: self.setOut(mOutput), wraplength=300) yplacement += 30 btn.place(x=0, y=yplacement, width=320, height=30) yplacement += 15 btnClose = BtnBlack20(self.window, text="Close", command=self.quitConfig) btnClose.place(x=80, y=380, width=160, height=80) def setIn(self, mInput): print("input selected: ", mInput) # TODO : check if it works self.midiInstance.setMidiInput(mInput) self.config["MIDI_interface_in"] = mInput self.labelCurrentIn.config(text="actual In is : " + mInput) def setOut(self, mOutput): # TODO : check if it works print("output selected:", mOutput) self.midiInstance.setMidiOutput(mOutput) self.config["MIDI_interface_out"] = mOutput self.labelCurrentOut.config(text="actual Out is : " + mOutput) def quitConfig(self): self.window.destroy() def destroy(self): print("trying destroy") del self def loadInitialSettings(self): value = int(self.config["question_delay"]) print(value) self.parent.slider1_1.set(value)
def __init__(self, globalRoot, choosenBpm, bassNote, chordQuality, backtrack, backtrackDuration, nbOfLoops, chordNotes, app): # images self.recImage = ImageTk.PhotoImage(Image.open(env.RECORD_IMAGE)) self.app = app self.globalRoot = globalRoot self.midiIO = Autoload().getInstance() self.midiIO.setCallback(self.handleMIDIInput) self.audioInstance = Autoload().getInstanceAudio() self.backtrack = backtrack self.backtrackDuration = backtrackDuration self.nbOfLoops = nbOfLoops self.choosenBpm = choosenBpm self.chordQuality = chordQuality self.bassNote = bassNote self.window = tk.Toplevel(self.globalRoot, cursor="none") self.window.attributes("-fullscreen", True) self.window.geometry("320x480") self.window["bg"] = "black" print("backtrack gui:", self.backtrack) self.chordNotes = chordNotes self.melodyNotes = [] self.customSignals = [] self.window.lbl1 = MyLabel12(self.window, text="Please record now...", wraplength=280) self.stringNotes = "" self.window.lbl2 = MyLabel40(self.window, text="{} {}".format( noteName(self.bassNote), self.chordQuality)) self.window.lbl3 = MyLabel24(self.window, text="Notes : " + self.stringNotes, wraplength=280) self.window.lblRec = MyLabel18(self.window, text="") # Buttons self.window.btnCancel = BtnBlack12(self.window, text="Cancel", command=self.cancel) self.window.btnRetry = BtnBlack12(self.window, text="Retry", command=self.retry) self.window.btnSave = BtnBlack12(self.window, text="Save", command=lambda: self.saveMidi()) # self.window.btnSave.config(state="disabled") self.window.canvas = tk.Canvas(self.window, bd=0, highlightthickness=0) self.window.lbl1.place(x=0, y=40, width=320, height=80) self.window.lbl2.place(x=0, y=120, width=320, height=60) self.window.lbl3.place(x=0, y=180, width=320, height=60) self.window.lblRec.place(x=20, y=250, width=280, height=40) self.window.btnCancel.place(x=20, y=310, width=100, height=130) self.window.btnRetry.place(x=120, y=310, width=80, height=130) self.window.btnSave.place(x=200, y=310, width=100, height=130) self.window.canvas.place(x=20, y=290, width=280, height=10) self.isRecording = False self.precountTimer = Bpm(self.choosenBpm, self.backtrack, self.backtrackDuration, self.nbOfLoops, lambda: self.activateRecording()) self.startingTime = 0 self.damper = [] self.damperIsActive = False
def __init__(self, globalRoot, backtrackFile, backtrackDuration, nbOfLoops, app): self.app = app self.globalRoot = globalRoot self.midiIO = Autoload().getInstance() self.midiIO.setCallback(self.handleMIDIInput) self.backtrackFile = backtrackFile self.backtrackDuration = backtrackDuration self.nbOfLoops = nbOfLoops self.isRecording = False print("backtrack : ", backtrackFile, backtrackDuration, nbOfLoops) self.window = tk.Toplevel(self.globalRoot) self.window.config(cursor="none") self.window.geometry("320x480") self.window.attributes("-fullscreen", True) self.window["bg"] = "black" # creation of 2 labels and 2 buttons self.window.lbl1 = MyLabel12( self.window, text="Insert Bass reference note and click on chord type.", wraplength=280) # show window the detected bass self.window.lbl2 = MyLabel18(self.window, text="?") self.window.lbl2.config(foreground="red") # lable which show the bass entered by the user self.window.lblBass = MyLabel18(self.window, text="?") self.window.lblBass.config(foreground="red") # buttons minor and major self.window.btnMinor = BtnBlack12(self.window, text="Minor") self.window.btnMinor.config( command=lambda: self.setChordQuality("minor")) self.window.btnMajor = BtnBlack12(self.window, text="Major") self.window.btnMajor.config( command=lambda: self.setChordQuality("major")) self.window.btndom7 = BtnBlack12(self.window, text="Dom7") self.window.btndom7.config( command=lambda: self.setChordQuality("dom7")) # slider self.window.bpmScale = tk.Scale(self.window, from_=40, to=140, resolution=1, orient=tk.HORIZONTAL, showvalue=0) self.window.bpmScale.config(command=self.updateBpmValue) self.window.bpmScale.config(background="black", foreground="white", sliderlength=80, width=90, bd=0) self.window.bpmScale.set(60) # default value for bpm record # custom progression self.window.btnCustom = BtnBlack12(self.window, text="OK", wraplength=130) self.window.btnCustom.config(command=self.validateBeforeShowingWindow) # Button cancel self.window.btnCancel = BtnBlack12(self.window, text="Cancel") self.window.btnCancel.config(command=self.cancel) # ----Placement------ yplacement = 10 self.window.lbl1.place(x=0, y=yplacement, width=320, height=60) yplacement += 70 self.window.lblBass.place(x=80, y=yplacement, width=60, height=60) self.window.lbl2.place(x=150, y=yplacement, width=100, height=60) yplacement += 80 self.window.btnMinor.place(x=25, y=yplacement, width=90, height=60) self.window.btnMajor.place(x=115, y=yplacement, width=90, height=60) self.window.btndom7.place(x=205, y=yplacement, width=90, height=60) # self.window.bpmScale.place(x=40, y=260, width=240, height=90) yplacement += 100 self.window.btnCustom.place(x=25, y=yplacement, width=270, height=60) yplacement += 70 self.window.btnCancel.place(x=25, y=yplacement, width=270, height=60) self.bassNote = 0 # reinitilisation of the bassnote self.chordQuality = "-"