From 18aac8b5ec7e8413a6f3d1723debfc126047cf85 Mon Sep 17 00:00:00 2001 From: Nelli Skogman Date: Fri, 5 Dec 2025 19:59:32 +0100 Subject: [PATCH 1/7] Run `scala fmt` --- .../src/main/scala/music/Chord.scala | 8 +-- .../src/main/scala/music/ChordPlayer.scala | 13 +++-- .../src/main/scala/music/Pitch.scala | 18 +++---- .../src/main/scala/music/Synth.scala | 52 ++++++++++++------- .../src/main/scala/music/commands.scala | 17 +++--- .../src/main/scala/music/instruments.scala | 13 ++--- 6 files changed, 66 insertions(+), 55 deletions(-) diff --git a/workspace/w13_music_proj/src/main/scala/music/Chord.scala b/workspace/w13_music_proj/src/main/scala/music/Chord.scala index b9569dc1f..b8d630983 100644 --- a/workspace/w13_music_proj/src/main/scala/music/Chord.scala +++ b/workspace/w13_music_proj/src/main/scala/music/Chord.scala @@ -3,22 +3,22 @@ package music case class Chord(ps: Vector[Pitch]): assert(ps.nonEmpty, "Chord pitch sequence is empty") - val pitchClasses: Vector[Int] = ps.map(_.pitchClass).toVector + val pitchClasses: Vector[Int] = ps.map(_.pitchClass).toVector def apply(i: Int): Pitch = ps(i) def intervals(root: Pitch = ps(0)): Vector[Int] = ps.map(_.nbr - root.nbr) def relativePitchClasses(root: Pitch = ps(0)): Vector[Int] = - intervals(root).map(i => (i%12 + 12) % 12).distinct.sorted + intervals(root).map(i => (i % 12 + 12) % 12).distinct.sorted def name(root: Pitch = ps(0)): String = relativePitchClasses(root) match case Vector(0, 4, 7) => root.pitchClassName case Vector(0, 3, 7) => root.pitchClassName + "m" case Vector(0, 4, 7, 10) => root.pitchClassName + "7" - case _ => root.pitchClassName + intervals(root).mkString("[",",","]") + case _ => root.pitchClassName + intervals(root).mkString("[", ",", "]") - override def toString = ps.map(_.name).mkString("Chord(",",",")") + override def toString = ps.map(_.name).mkString("Chord(", ",", ")") object Chord: def apply(xs: String*): Chord = Chord(xs.map(Pitch.apply).toVector) diff --git a/workspace/w13_music_proj/src/main/scala/music/ChordPlayer.scala b/workspace/w13_music_proj/src/main/scala/music/ChordPlayer.scala index 9324bd874..355e824ac 100644 --- a/workspace/w13_music_proj/src/main/scala/music/ChordPlayer.scala +++ b/workspace/w13_music_proj/src/main/scala/music/ChordPlayer.scala @@ -1,15 +1,14 @@ package music object ChordPlayer: - + case class Strike( - velocity: Int = 50, // hur hårt anslag i Range(0, 128) - duration: Long = 500, // hur länge i millisekunder - spread: Long = 50, // millisekunder mellan tonerna - after: Long = 0 // millisekunder innan första tonen + velocity: Int = 50, // hur hårt anslag i Range(0, 128) + duration: Long = 500, // hur länge i millisekunder + spread: Long = 50, // millisekunder mellan tonerna + after: Long = 0 // millisekunder innan första tonen ) def play(chord: Chord, strike: Strike = Strike(), channel: Int = 0): Unit = - strike match + strike match case Strike(v, d, s, a) => ??? - diff --git a/workspace/w13_music_proj/src/main/scala/music/Pitch.scala b/workspace/w13_music_proj/src/main/scala/music/Pitch.scala index 7e0559a50..dcdccc716 100644 --- a/workspace/w13_music_proj/src/main/scala/music/Pitch.scala +++ b/workspace/w13_music_proj/src/main/scala/music/Pitch.scala @@ -1,17 +1,17 @@ package music -case class Pitch(nbr: Int): //Tonhöjd +case class Pitch(nbr: Int): // Tonhöjd assert((0 to 127) contains nbr, s"Error: nbr $nbr outside (0 to 127)") - def pitchClass: Int = nbr % 12 + def pitchClass: Int = nbr % 12 def pitchClassName: String = Pitch.pitchClassNames(pitchClass) - def name: String = s"$pitchClassName$octave" - def octave: Int = nbr / 12 - def +(offset: Int): Pitch = Pitch(nbr + offset) - override def toString = s"Pitch($name)" + def name: String = s"$pitchClassName$octave" + def octave: Int = nbr / 12 + def +(offset: Int): Pitch = Pitch(nbr + offset) + override def toString = s"Pitch($name)" object Pitch: - val defaultOctave = 5 // mittenoktaven på ett pianos tangentbord - + val defaultOctave = 5 // mittenoktaven på ett pianos tangentbord + val pitchClassNames: Vector[String] = Vector("C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B") @@ -20,7 +20,7 @@ object Pitch: def fromString(s: String): Option[Pitch] = scala.util.Try { val (pitchClassName, octaveName) = s.partition(c => !c.isDigit) val octave = if octaveName.nonEmpty then octaveName.toInt else 5 - Pitch(pitchClassIndex(pitchClassName) + octave * 12) + Pitch(pitchClassIndex(pitchClassName) + octave * 12) }.toOption def apply(s: String): Pitch = diff --git a/workspace/w13_music_proj/src/main/scala/music/Synth.scala b/workspace/w13_music_proj/src/main/scala/music/Synth.scala index 0fa4a8225..c65ec6d28 100644 --- a/workspace/w13_music_proj/src/main/scala/music/Synth.scala +++ b/workspace/w13_music_proj/src/main/scala/music/Synth.scala @@ -9,23 +9,35 @@ object Synth: println(MidiSystem.getMidiDeviceInfo().mkString(" ")) val synth = MidiSystem.getSynthesizer synth.open - assert(synth.loadAllInstruments(synth.getDefaultSoundbank), "Loading MIDI instruments failed") + assert( + synth.loadAllInstruments(synth.getDefaultSoundbank), + "Loading MIDI instruments failed" + ) synth resetInstruments() // assign some different instruments to channels - def midiChannel(channel: Int): MidiChannel = underlying.getChannels.apply(channel) + def midiChannel(channel: Int): MidiChannel = + underlying.getChannels.apply(channel) def channels: Range = underlying.getChannels.indices def instruments: Seq[Instrument] = underlying.getLoadedInstruments.toSeq def changeInstrument(program: Int, channel: Int = 0): Unit = - val patch = instruments.find(_.getPatch.getProgram == program).map(_.getPatch) + val patch = + instruments.find(_.getPatch.getProgram == program).map(_.getPatch) patch match - case Some(p) => midiChannel(channel).programChange(p.getBank, p.getProgram) + case Some(p) => + midiChannel(channel).programChange(p.getBank, p.getProgram) case None => println(s"Instrument with program number $program not found") - lazy val defaultInstruments = - Vector(AcousticGrandPiano, AcousticGuitarNylon, AcousticBass, Trumpet, Flute) + lazy val defaultInstruments = + Vector( + AcousticGrandPiano, + AcousticGuitarNylon, + AcousticBass, + Trumpet, + Flute + ) def resetInstruments(): Unit = defaultInstruments.zipWithIndex.foreach { case (program, channel) => changeInstrument(program, channel) @@ -44,15 +56,15 @@ object Synth: def delay(millis: Long): Unit = Thread.sleep(millis) def playBlocking( - noteNumbers: Seq[Int] = Vector(60), - velocity: Int = 60, - duration: Long = 300, - spread: Long = 50, - after: Long = 0, - channel: Int = 0 + noteNumbers: Seq[Int] = Vector(60), + velocity: Int = 60, + duration: Long = 300, + spread: Long = 50, + after: Long = 0, + channel: Int = 0 ): Unit = delay(after) - noteNumbers.foreach{ nbr => + noteNumbers.foreach { nbr => noteOn(nbr, velocity, channel) delay(spread) } @@ -60,16 +72,16 @@ object Synth: noteNumbers.foreach(noteOff(_, channel)) def playConcurrently( - noteNumbers: Seq[Int] = Vector(60), - velocity: Int = 60, - duration: Long = 300, - spread: Long = 50, - after: Long = 0, - channel: Int = 0 + noteNumbers: Seq[Int] = Vector(60), + velocity: Int = 60, + duration: Long = 300, + spread: Long = 50, + after: Long = 0, + channel: Int = 0 ): Unit = import scala.concurrent.ExecutionContext.Implicits.global - val _ = scala.concurrent.Future{ + val _ = scala.concurrent.Future { playBlocking(noteNumbers, velocity, duration, spread, after, channel) } diff --git a/workspace/w13_music_proj/src/main/scala/music/commands.scala b/workspace/w13_music_proj/src/main/scala/music/commands.scala index 067b29ab3..f54e2844e 100644 --- a/workspace/w13_music_proj/src/main/scala/music/commands.scala +++ b/workspace/w13_music_proj/src/main/scala/music/commands.scala @@ -5,37 +5,36 @@ abstract class Command(val str: String, val help: String): object Command: val all: Seq[Command] = Seq(Help, Quit, Play) - val allHelpTexts: String = - Command.all.map(c => c.str.padTo(10,' ') + c.help).mkString("\n") + val allHelpTexts: String = + Command.all.map(c => c.str.padTo(10, ' ') + c.help).mkString("\n") def find(command: String): Option[Command] = all.find(_.str == command) def apply(cmd: String, args: Seq[String]): String = all.find(_.str == cmd) match case Some(c) => c(args) - case None => s"Unknown command: $cmd\nType ? for help." + case None => s"Unknown command: $cmd\nType ? for help." def loopUntilExit(nextLine: () => String): Unit = val line = nextLine() if line != null then val result = line.split(' ').toSeq match - case Seq() => "" + case Seq() => "" case cmd +: args => Command(cmd, args) if result != "" then println(result) if result != Main.exitMsg then loopUntilExit(nextLine) - else - println("\n" + Main.exitMsg) + else println("\n" + Main.exitMsg) object Help extends Command("?", "print help"): def apply(args: Seq[String]): String = args match - case Seq() => Command.allHelpTexts + case Seq() => Command.allHelpTexts case Seq(cmd) => Command.find(cmd).map(_.help).getOrElse(s"Unknown: $cmd") - case _ => s"Usage: $str [cmd]" + case _ => s"Usage: $str [cmd]" object Quit extends Command(":q", "quit this app"): def apply(args: Seq[String]): String = args match case Seq() => Main.exitMsg - case _ => s"Error: $args after :q not allowed" + case _ => s"Error: $args after :q not allowed" object Play extends Command("!", "play chord TODO"): def apply(args: Seq[String]): String = args match diff --git a/workspace/w13_music_proj/src/main/scala/music/instruments.scala b/workspace/w13_music_proj/src/main/scala/music/instruments.scala index e5d5ccbd6..0cd3b5837 100644 --- a/workspace/w13_music_proj/src/main/scala/music/instruments.scala +++ b/workspace/w13_music_proj/src/main/scala/music/instruments.scala @@ -11,20 +11,21 @@ case class Piano(isKeyDown: Set[Int]) extends StringInstrument: trait FrettedInstrument extends StringInstrument: def nbrOfStrings: Int def tuning: Vector[Pitch] - def grip: Vector[Int] - def toChordOpt: Option[Chord] = - val notes = - for i <- grip.indices if grip(i) >= 0 + def grip: Vector[Int] + def toChordOpt: Option[Chord] = + val notes = + for i <- grip.indices if grip(i) >= 0 yield tuning(i) + grip(i) if notes.nonEmpty then Some(Chord(notes.toVector)) else None -case class Guitar(pos: (Int,Int,Int,Int,Int,Int)) extends FrettedInstrument: +case class Guitar(pos: (Int, Int, Int, Int, Int, Int)) + extends FrettedInstrument: val grip = Vector(pos._1, pos._2, pos._3, pos._4, pos._5, pos._6) val nbrOfStrings = 6 val tuning = "E3 A3 D4 G4 B4 E5".split(' ').map(Pitch.apply).toVector -case class Ukulele(pos: (Int,Int,Int,Int)) extends FrettedInstrument: +case class Ukulele(pos: (Int, Int, Int, Int)) extends FrettedInstrument: val grip = Vector(pos._1, pos._2, pos._3, pos._4) val nbrOfStrings = 4 val tuning = From ad7ff8360320db63eaef98bf0447ec8fb365d2d1 Mon Sep 17 00:00:00 2001 From: Nelli Skogman Date: Fri, 5 Dec 2025 20:13:43 +0100 Subject: [PATCH 2/7] Update Scala 2 syntax --- workspace/w13_music_proj/src/main/scala/music/Main.scala | 2 +- workspace/w13_music_proj/src/main/scala/music/Synth.scala | 7 +++---- .../w13_music_proj/src/main/scala/music/commands.scala | 4 +++- .../w13_music_proj/src/main/scala/music/instruments.scala | 3 ++- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/workspace/w13_music_proj/src/main/scala/music/Main.scala b/workspace/w13_music_proj/src/main/scala/music/Main.scala index 3dcd4081a..40276551a 100644 --- a/workspace/w13_music_proj/src/main/scala/music/Main.scala +++ b/workspace/w13_music_proj/src/main/scala/music/Main.scala @@ -7,4 +7,4 @@ object Main: def main(args: Array[String]): Unit = println(helloMsg) Synth.playBlocking() - Command.loopUntilExit(readLine _) + Command.loopUntilExit(readLine) diff --git a/workspace/w13_music_proj/src/main/scala/music/Synth.scala b/workspace/w13_music_proj/src/main/scala/music/Synth.scala index c65ec6d28..52ed58a69 100644 --- a/workspace/w13_music_proj/src/main/scala/music/Synth.scala +++ b/workspace/w13_music_proj/src/main/scala/music/Synth.scala @@ -1,8 +1,8 @@ package music object Synth: - import javax.sound.midi._ - import GMInstruments._ + import javax.sound.midi.* + import GMInstruments.* val underlying: Synthesizer = println("Initializing javax.sound.MidiSystem ...") @@ -81,9 +81,8 @@ object Synth: ): Unit = import scala.concurrent.ExecutionContext.Implicits.global - val _ = scala.concurrent.Future { + val _ = scala.concurrent.Future: playBlocking(noteNumbers, velocity, duration, spread, after, channel) - } object GMInstruments: // These program numbers are defined as particular instruments by General MIDI. diff --git a/workspace/w13_music_proj/src/main/scala/music/commands.scala b/workspace/w13_music_proj/src/main/scala/music/commands.scala index f54e2844e..94b6c4343 100644 --- a/workspace/w13_music_proj/src/main/scala/music/commands.scala +++ b/workspace/w13_music_proj/src/main/scala/music/commands.scala @@ -38,4 +38,6 @@ object Quit extends Command(":q", "quit this app"): object Play extends Command("!", "play chord TODO"): def apply(args: Seq[String]): String = args match - case _ => Synth.playBlocking(); s"play chord TODO" + case _ => + Synth.playBlocking() + s"play chord TODO" diff --git a/workspace/w13_music_proj/src/main/scala/music/instruments.scala b/workspace/w13_music_proj/src/main/scala/music/instruments.scala index 0cd3b5837..ac0c538dd 100644 --- a/workspace/w13_music_proj/src/main/scala/music/instruments.scala +++ b/workspace/w13_music_proj/src/main/scala/music/instruments.scala @@ -1,6 +1,7 @@ package music -trait StringInstrument { def toChordOpt: Option[Chord] } +trait StringInstrument: + def toChordOpt: Option[Chord] case class Piano(isKeyDown: Set[Int]) extends StringInstrument: def toChordOpt: Option[Chord] = From a0a68a814bd3b95fcb4e00a8067288f6db590b8d Mon Sep 17 00:00:00 2001 From: Nelli Skogman Date: Fri, 5 Dec 2025 20:58:47 +0100 Subject: [PATCH 3/7] Refactor `Pitch` to use scientific pitch notation --- .../w13_music_proj/src/main/scala/music/Pitch.scala | 10 +++++----- .../src/main/scala/music/instruments.scala | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/workspace/w13_music_proj/src/main/scala/music/Pitch.scala b/workspace/w13_music_proj/src/main/scala/music/Pitch.scala index dcdccc716..e14a7db7a 100644 --- a/workspace/w13_music_proj/src/main/scala/music/Pitch.scala +++ b/workspace/w13_music_proj/src/main/scala/music/Pitch.scala @@ -5,12 +5,12 @@ case class Pitch(nbr: Int): // Tonhöjd def pitchClass: Int = nbr % 12 def pitchClassName: String = Pitch.pitchClassNames(pitchClass) def name: String = s"$pitchClassName$octave" - def octave: Int = nbr / 12 + def octave: Int = nbr / 12 - 1 def +(offset: Int): Pitch = Pitch(nbr + offset) override def toString = s"Pitch($name)" object Pitch: - val defaultOctave = 5 // mittenoktaven på ett pianos tangentbord + val defaultOctave = 4 // mittenoktaven på ett pianos tangentbord val pitchClassNames: Vector[String] = Vector("C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B") @@ -18,9 +18,9 @@ object Pitch: val pitchClassIndex: Map[String, Int] = pitchClassNames.zipWithIndex.toMap def fromString(s: String): Option[Pitch] = scala.util.Try { - val (pitchClassName, octaveName) = s.partition(c => !c.isDigit) - val octave = if octaveName.nonEmpty then octaveName.toInt else 5 - Pitch(pitchClassIndex(pitchClassName) + octave * 12) + val (pitchClassName, octaveName) = s.partition(c => c.isLetter) + val octave = if octaveName.nonEmpty then octaveName.toInt else defaultOctave + Pitch(pitchClassIndex(pitchClassName) + (octave + 1) * 12) }.toOption def apply(s: String): Pitch = diff --git a/workspace/w13_music_proj/src/main/scala/music/instruments.scala b/workspace/w13_music_proj/src/main/scala/music/instruments.scala index ac0c538dd..64436b725 100644 --- a/workspace/w13_music_proj/src/main/scala/music/instruments.scala +++ b/workspace/w13_music_proj/src/main/scala/music/instruments.scala @@ -24,10 +24,10 @@ case class Guitar(pos: (Int, Int, Int, Int, Int, Int)) val grip = Vector(pos._1, pos._2, pos._3, pos._4, pos._5, pos._6) val nbrOfStrings = 6 val tuning = - "E3 A3 D4 G4 B4 E5".split(' ').map(Pitch.apply).toVector + "E2 A2 D3 G3 B3 E4".split(' ').map(Pitch.apply).toVector case class Ukulele(pos: (Int, Int, Int, Int)) extends FrettedInstrument: val grip = Vector(pos._1, pos._2, pos._3, pos._4) val nbrOfStrings = 4 val tuning = - "A5 D5 F#5 B5".split(' ').map(Pitch.apply).toVector + "A4 D4 F#4 B4".split(' ').map(Pitch.apply).toVector From 27f4fd4148ffe235ca188cbcd767db9457f0d667 Mon Sep 17 00:00:00 2001 From: Nelli Skogman Date: Fri, 5 Dec 2025 21:04:05 +0100 Subject: [PATCH 4/7] Remove unnecessary brackets --- workspace/w13_music_proj/src/main/scala/music/Chord.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/workspace/w13_music_proj/src/main/scala/music/Chord.scala b/workspace/w13_music_proj/src/main/scala/music/Chord.scala index b8d630983..dc89869d9 100644 --- a/workspace/w13_music_proj/src/main/scala/music/Chord.scala +++ b/workspace/w13_music_proj/src/main/scala/music/Chord.scala @@ -23,6 +23,6 @@ case class Chord(ps: Vector[Pitch]): object Chord: def apply(xs: String*): Chord = Chord(xs.map(Pitch.apply).toVector) - def random(pitchNumbers: Seq[Int] = (60 to 72), n: Int = 3): Chord = + def random(pitchNumbers: Seq[Int] = 60 to 72, n: Int = 3): Chord = val shuffled = scala.util.Random.shuffle(pitchNumbers).toVector Chord(shuffled.take(n).map(Pitch.apply)) From bcacb657cb52e4226aeb09d7323f97f8f1e5de0e Mon Sep 17 00:00:00 2001 From: Nelli Skogman Date: Mon, 8 Dec 2025 13:35:29 +0100 Subject: [PATCH 5/7] Import stuff I've chosen to import names at the file level unless other related names were imported in a tighter scope. I did this to make function bodies smaller and since I felt it was waranted in most cases. --- workspace/w13_music_proj/src/main/scala/music/Chord.scala | 4 +++- workspace/w13_music_proj/src/main/scala/music/Main.scala | 4 +++- workspace/w13_music_proj/src/main/scala/music/Pitch.scala | 4 +++- workspace/w13_music_proj/src/main/scala/music/Synth.scala | 3 ++- 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/workspace/w13_music_proj/src/main/scala/music/Chord.scala b/workspace/w13_music_proj/src/main/scala/music/Chord.scala index dc89869d9..8d0af5af5 100644 --- a/workspace/w13_music_proj/src/main/scala/music/Chord.scala +++ b/workspace/w13_music_proj/src/main/scala/music/Chord.scala @@ -1,5 +1,7 @@ package music +import scala.util.Random + case class Chord(ps: Vector[Pitch]): assert(ps.nonEmpty, "Chord pitch sequence is empty") @@ -24,5 +26,5 @@ object Chord: def apply(xs: String*): Chord = Chord(xs.map(Pitch.apply).toVector) def random(pitchNumbers: Seq[Int] = 60 to 72, n: Int = 3): Chord = - val shuffled = scala.util.Random.shuffle(pitchNumbers).toVector + val shuffled = Random.shuffle(pitchNumbers).toVector Chord(shuffled.take(n).map(Pitch.apply)) diff --git a/workspace/w13_music_proj/src/main/scala/music/Main.scala b/workspace/w13_music_proj/src/main/scala/music/Main.scala index 40276551a..5c91fa0ee 100644 --- a/workspace/w13_music_proj/src/main/scala/music/Main.scala +++ b/workspace/w13_music_proj/src/main/scala/music/Main.scala @@ -1,8 +1,10 @@ package music +import scala.io.StdIn + object Main: val (helloMsg, exitMsg) = ("*** Welcome to music!", "Goodbye music!") - def readLine(): String = scala.io.StdIn.readLine("music> ") + def readLine(): String = StdIn.readLine("music> ") def main(args: Array[String]): Unit = println(helloMsg) diff --git a/workspace/w13_music_proj/src/main/scala/music/Pitch.scala b/workspace/w13_music_proj/src/main/scala/music/Pitch.scala index e14a7db7a..87675d36a 100644 --- a/workspace/w13_music_proj/src/main/scala/music/Pitch.scala +++ b/workspace/w13_music_proj/src/main/scala/music/Pitch.scala @@ -1,5 +1,7 @@ package music +import scala.util.Try + case class Pitch(nbr: Int): // Tonhöjd assert((0 to 127) contains nbr, s"Error: nbr $nbr outside (0 to 127)") def pitchClass: Int = nbr % 12 @@ -17,7 +19,7 @@ object Pitch: val pitchClassIndex: Map[String, Int] = pitchClassNames.zipWithIndex.toMap - def fromString(s: String): Option[Pitch] = scala.util.Try { + def fromString(s: String): Option[Pitch] = Try { val (pitchClassName, octaveName) = s.partition(c => c.isLetter) val octave = if octaveName.nonEmpty then octaveName.toInt else defaultOctave Pitch(pitchClassIndex(pitchClassName) + (octave + 1) * 12) diff --git a/workspace/w13_music_proj/src/main/scala/music/Synth.scala b/workspace/w13_music_proj/src/main/scala/music/Synth.scala index 52ed58a69..dff54987a 100644 --- a/workspace/w13_music_proj/src/main/scala/music/Synth.scala +++ b/workspace/w13_music_proj/src/main/scala/music/Synth.scala @@ -80,8 +80,9 @@ object Synth: channel: Int = 0 ): Unit = import scala.concurrent.ExecutionContext.Implicits.global + import scala.concurrent.Future - val _ = scala.concurrent.Future: + val _ = Future: playBlocking(noteNumbers, velocity, duration, spread, after, channel) object GMInstruments: From ff79388edacaa63bceaf1d5da0b38e5bcf6a9e01 Mon Sep 17 00:00:00 2001 From: Nelli Skogman Date: Mon, 8 Dec 2025 13:44:57 +0100 Subject: [PATCH 6/7] Use `math.floorMod` to calculate `relativePitchClasses` --- workspace/w13_music_proj/src/main/scala/music/Chord.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/workspace/w13_music_proj/src/main/scala/music/Chord.scala b/workspace/w13_music_proj/src/main/scala/music/Chord.scala index 8d0af5af5..8b53ee35c 100644 --- a/workspace/w13_music_proj/src/main/scala/music/Chord.scala +++ b/workspace/w13_music_proj/src/main/scala/music/Chord.scala @@ -1,5 +1,6 @@ package music +import scala.math.floorMod import scala.util.Random case class Chord(ps: Vector[Pitch]): @@ -12,7 +13,7 @@ case class Chord(ps: Vector[Pitch]): def intervals(root: Pitch = ps(0)): Vector[Int] = ps.map(_.nbr - root.nbr) def relativePitchClasses(root: Pitch = ps(0)): Vector[Int] = - intervals(root).map(i => (i % 12 + 12) % 12).distinct.sorted + intervals(root).map(i => floorMod(i, 12)).distinct.sorted def name(root: Pitch = ps(0)): String = relativePitchClasses(root) match case Vector(0, 4, 7) => root.pitchClassName From 25309593dedf98580f93e49e8fc8ee8d87a150d2 Mon Sep 17 00:00:00 2001 From: Nelli Skogman Date: Mon, 8 Dec 2025 13:54:01 +0100 Subject: [PATCH 7/7] Rename "relative pitch classes" to "simple intervals" It is the more common name withing music theory for this. One could call this "intervall classes" to be in line with "pitch classes" since they both are equivalence classes. --- workspace/w13_music_proj/src/main/scala/music/Chord.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/workspace/w13_music_proj/src/main/scala/music/Chord.scala b/workspace/w13_music_proj/src/main/scala/music/Chord.scala index 8b53ee35c..046d09709 100644 --- a/workspace/w13_music_proj/src/main/scala/music/Chord.scala +++ b/workspace/w13_music_proj/src/main/scala/music/Chord.scala @@ -12,10 +12,10 @@ case class Chord(ps: Vector[Pitch]): def intervals(root: Pitch = ps(0)): Vector[Int] = ps.map(_.nbr - root.nbr) - def relativePitchClasses(root: Pitch = ps(0)): Vector[Int] = + def simpleIntervals(root: Pitch = ps(0)): Vector[Int] = intervals(root).map(i => floorMod(i, 12)).distinct.sorted - def name(root: Pitch = ps(0)): String = relativePitchClasses(root) match + def name(root: Pitch = ps(0)): String = simpleIntervals(root) match case Vector(0, 4, 7) => root.pitchClassName case Vector(0, 3, 7) => root.pitchClassName + "m" case Vector(0, 4, 7, 10) => root.pitchClassName + "7"