DIY Ear training with Python and Music21, part 2
Having a basic ear training app (as described in part 1 working, I started to use it. As I mentioned in my last post I quickly discovered I was surprisingly bad at it, and my bare bones app was not helping me as much as I thought it could.
Correcting and learning from mistakes
Not having had much experience with recognizing intervals, I decided to keep things simple and start with just 3 intervals, the major third, the perfect fifth, and a perfect octave. Since I was making plenty of mistakes, I started thinking about how to correct those mistakes. For a while I tried repeating an interval until I got it right, but considering the limited choices (major third, perfect fifth, and octave) and the fact that I was showing the right answer after the fact, that wasn't much help.
I was also finding that I was struggling most with telling a perfect fifth and an octave apart. I have no idea if this is a common issue, but it seemed to me that they are both similarly consonant, and unless I was careful (and all too often even if I was careful) they sounded similar to me.
My approach was first, reduce the possible choices to just those two. I could always add thirds back in later, but first I wanted to get past the fifth/octave confusion. Next I tried creating a cell that picked notes at random and then played the possible intervals. Warming up with this approach seemed to improve performance at least for a session immediately after that.
That said, I do wonder if that approach might be worth trying. but only playing the samples randomly, only part of the time, and progressively less often. Another option might be to play only one reference tone, say the octave or fifth, to help set the stage.
That said, I do wonder if that approach might be worth trying but only playing the samples randomly only part of the time, and progressively less often, might be worth investigating. Another option might be to play only one reference tone, say the octave of fifth, to help set the stage.
Finally, I decided to add code to review the mistakes. Since I was already collecting the incorrect answers, along with the notes and correct interval, it was easy enough to instead add a review of wrong answers after the main run was finished. This randomly selected items from the list of incorrect answers, only removing them when they were answered correctly. After trying it a time or two, I decided to double the list of incorrect choices (incorrect = incorrect * 2
), just to give me extra practice with the trouble spots.
Improving feedback
In addition to handling the errors, I had found the feedback was not clear enough. If I made and error, I wanted it to be a little bit obnoxious about it. (As you might guess I'm not totally against negative feedback).
I decided that playing an irritating noise might be a good way to handle indicate an incorrect guess. I wasn't able to get the Jupyter Audio component to play a sound automatically during a loop (although it would as the main code in a cell), so instead I went with the playback3 library and a gong .wav file I found in a quick search. This, along with some clearer messages and a count of the total attempts in a session, gave me much better feedback.
The current code
The actual code was this:
choice = "" correct = [] incorrect = [] total_tries = 0 while choice.lower() != 'q': total_tries += 1 test_interval = random.choice(intervals) base_note_name = random.choice(chromatic_guitar[:-12]) base_note = note.Note(base_note_name) test_stream = stream.Stream([base_note, base_note.transpose(test_interval)]) test_stream.show("midi") choice = input("interval: ") clear_output() if choice == test_interval: correct.append(choice) print(f"{choice} is correct\n") elif choice.lower() == 'q': total_tries -= 1 break else: playsound("gong.wav") incorrect.append((test_interval, base_note, choice, base_note.transpose(test_interval))) print(f"{choice} is incorrect\n") print(f"{len(correct)}/{len(correct)+len(incorrect)}") print(f"{base_note.name}, {base_note.transpose(test_interval).name} --> {test_interval}, {choice}") incorrect = incorrect *2 while incorrect: total_tries += 1 print(f"Reviewing {len(incorrect)} missed items") test_item = random.choice(incorrect) test_stream = stream.Stream([ test_item[1], test_item[3], ] ) test_stream.show("midi") choice = input("interval: ") clear_output() if choice == test_item[0]: correct.append(choice) incorrect.remove(test_item) print(f"{choice} is correct\n") elif choice.lower() == 'q': total_tries -= 1 break else: playsound("gong.wav") print(f"{choice} is incorrect\n") print(f"{len(correct)}/{len(correct)+len(incorrect)}") print(f"{test_item[1].name}, {test_item[3].name} --> {test_item[0]}, {choice}") percentage = float(len(correct)/total_tries * 100.0 if total_tries else 0) print(f"{total_tries} tries for {len(correct) } intervals correct = {percentage:02.1f}%\n") if not incorrect: print("All errors fixed") else: print(f"{len(incorrect)} errors not fixed")
Future options
This is still pretty simple minded. There is no attempt to track results or progress over time and no analysis of the types of errors which could shape remedial exercises. Both of those would require adding a SQLite database, which would be easy enough to do.
Beyond that, with a database, I could also add spaced repetition to the sessions, refreshing old work why moving on to new items. I had included that in my first notes on the project, but I'm not sure if I'll feel the need to implement it. If I do, I guess that would lead to a part 3 of the series.
Results
But even with it still being simple, I now had\ something that was starting to work as I'd hoped. I can fire up this notebook every day, work through 3 or 4 runs of 30 choices and get extra practice with my mistakes.
I've been pretty consistent in practicing every day, which I think is at least partly due to the fact that it's my code and I had to understand the problem in order to create it.
So far I've been happy with the results. After a few days of practice, I can now more reliably tell my fifths from my octaves, I've even added both major and minor thirds back in, and am surprised at how much easier those are for me.
If you want to leave a comment, or just prefer a more interactive experience, this post can also be found on Sustack at https://naomiceder.substack.com/p/diy-ear-training-with-python-and-2d5