Allow Musical Symbols in identifiers

tl:dr;

diff --git a/include/swift/AST/Identifier.h b/include/swift/AST/Identifier.h
index 316a91dd980f6..c7b39b70538ab 100644
--- a/include/swift/AST/Identifier.h
+++ b/include/swift/AST/Identifier.h
@@ -134,7 +134,7 @@ class Identifier {
     if (C < 0x80)
       return memchr(OpChars, C, sizeof(OpChars) - 1) != 0;
     
-    // Unicode math, symbol, arrow, dingbat, and line/box drawing chars.
+    // Unicode math, music, symbol, arrow, dingbat and line/box drawing chars.
     return (C >= 0x00A1 && C <= 0x00A7)
         || C == 0x00A9 || C == 0x00AB || C == 0x00AC || C == 0x00AE
         || C == 0x00B0 || C == 0x00B1 || C == 0x00B6 || C == 0x00BB
@@ -144,7 +144,8 @@ class Identifier {
         || (C >= 0x2055 && C <= 0x205E) || (C >= 0x2190 && C <= 0x23FF)
         || (C >= 0x2500 && C <= 0x2775) || (C >= 0x2794 && C <= 0x2BFF)
         || (C >= 0x2E00 && C <= 0x2E7F) || (C >= 0x3001 && C <= 0x3003)
-        || (C >= 0x3008 && C <= 0x3030);
+        || (C >= 0x3008 && C <= 0x3030)
+        || (C >= 0x1D100 && C <= 0x1D1FF);
   }
   
   /// isOperatorContinuationCodePoint - Return true if the specified code point

Hey Folks, I'm fairly new to Swift, so I hope you'll forgive my ignorance and disabuse me of it.

I'm currently on vacation and don't want to do anything that's related to my day to day job. It's kinda like I gave up mechanical keyboard for lent so I could recharge, and I would have gotten away with it too if it weren't for you meddling kids. But I've decided since my iPad's keyboard is virtual, if I type swift code in to swift playgrounds I'm technically not cheating :sweat_smile: ANYwhooooo

may we please be allowed to use musical symbols in identifiers so I can write this.

let 📲🎶= 𝄞{
    𝄀{F; C; A♯; C; F}    
    𝄀{G; G; A♯; C; C; A♯; G; C; F; C; A♯; C; F}.𝄇(3)
}

📲🎶.▶️()

Edit: I'm aware 𝄀 is a bar not a stave, but the stave symbol will look like this 𝄛 until someone adds some Music Fonts to the forums css :smile:

4 Likes

The piece of code you're patching is validation for the first character of operator identifiers, which is probably not what you're looking for. Additionally, "♯" (U+266F) is not in the musical symbols unicode block, and is already a legal operator head character, so it might not be possible to let it also be a valid (non-operator) identifier character.

Correct. You can already use the musical symbols block in identifiers:

let 𝄞 = 42

Swift's grammar does not allow a code point to be valid in both operators and identifiers—it must be one or the other. You can define as a custom postfix operator (this would make sense, actually) but not A♯ as a variable.

See Language Reference: Lexical Structure in The Swift Programming Language for a full list of valid identifier and operator code points.

2 Likes

Ah, so I would need to complain to Unicode for ∞ being in the Unicode Block Mathematical Operators rather than Mathematical Alphanumeric Symbols for let ∞ = Double.infinity failing. Thank you - I had been wondering about that.

2 Likes

But what octave is it? You'd need A#₃ or B♭₃.

-1 from me: you can as easily use [As3, Bb3].play()

Also do not forget duration / forte / piano / staccato, etc.

Aha! This makes so much more sense now.

Honestly it never occurred to me the operators would be more than a byte long, as most languages keep them single bytes, but I'm genuinely chuffed to see Swift hasn't! (What a forward thinking design decision :smile:)

Which is why I never tried to do

postfix operator ♭
postfix operator ♯

But once I did, it worked a charm!!!!!! This compiles and actually does useful things......

var 📲🎶 = 𝄞(Pitch.A440.C, (4,4)) {
    F; C; A♯; C; F
    G; G; A♯; C; C; A♯; G; C; F; C; A♯; C; F
    G; G; A♯; C; C; A♯; G; C; F; C; A♯; C; F
    G; G; A♯; C; C; A♯; G; C; F; C; A♯; C; F
}

Thank you both @xwu and @wowbagger!
I had to fill in a bunch of boilerplate code to make it work but it compiles and does the right things!!!

now I can write music in swift, I wonder if CoPilot can too :grin:
ezgif-5-fec3670508

16 Likes

Currently the compiler just returns a very generic parsing error when a operator character is used for (non-operator) identifier (at least in REPL):

1> let ♮ = 1
expression failed to parse:
error: repl.swift:1:5: error: expected pattern
let ♮ = 1

I think the diagnostics can be improved, so it can be less confusing for people who stumbles on it. Maybe we can add an educational note too.

5 Likes

I think this is an excellent idea, in that spirit I'd like to share how I stumbled on this so other people can avoid it :smile:

just to make it absolutely clear, I have something with postfix operators I really like, so I'm just writing my experience of what made me write up this post in the first place, in the hope it helps someone else.

I think I can explain this in one sentence,
Up to know assuming that Unicode consortium can make coherent design choices has served Swift well, but perhaps those days maybe over :grin:

let 𝄞 = 42 // ✅
let ♮ = 1 // ⛔️

postfix operator ♮ // ✅
postfix operator 𝄞 // ⛔️ this even confuses the syntax highlighter 😁

this is due to the fact

The most common accidentals are encoded in the Miscellaneous Symbols block.

symbol meaning codepoint decimal
flat 0x266D 9837
natural 0x266E 9838
sharp 0x266F 9839
𝄪 double sharp 0x1D12A 119082
𝄫 double flat 0x1D12B 119083

So some musical symbols are valid identifier code-points, few other musical symbols defined in miscellaneous blocks are valid operators. And since

Which makes things kinda awkward.

Just to reiterate, I'm aware one can do

postfix operator ♯♯

(which would introduce operator precedence issue, but whatever, probably not the end of the world)
I'm just writing my experience here so it might help someone else avoid the same mistakes :smile:

All that being said, this is still a wonderfully delightful experience, with resultBuilders and property wrappers and so on. Swift just took a huge leap in my favourite languages ranking...

I was playing around with this on my iPad at a random pub and someone sitting next to me asked what music composition program I was using and I smugly replied swift playgrounds* :smile:

*Batteries sold separately

1 Like

Seconded. If the diagnostic could both specific what Unicode block the character is in, and what Unicode blocks are allowed in that context that would elucidate much of the mystery.

This is a fundamentally flawed assumption for a specification that aims to be a superset of everyone else—and thereby a superset of everyone else’s design blunders. :wink:

7 Likes

Ha. Totally agree. it was more of a tongue-in-cheek comment trying to make the same point you did then an assumption, based solely on reading Jennifer Daniel's blog posts, but demonstrably I could've done a better job :smile:

I really don't understand a lot of what's being talked about here, but I'm super intrigued - would you be able to share access to this so I can learn/play around?

1 Like

This was the code I was using in playgrounds.

import PlaygroundSupport
import AudioToolbox


PlaygroundPage.current.needsIndefiniteExecution = true

postfix operator ♭
postfix operator ♯

@propertyWrapper
struct Pitch {
    var hertz: Double
    var wrappedValue: Double {
        get { hertz }
        set { hertz = newValue }
    }
    // Increases the pitch by 12th parts 
    static postfix func ♯(num: Pitch) -> Pitch {
        return Pitch(hertz: num.hertz * 1.0594630943592953)
    }
    // Decreases the pitch by 12th parts
    static postfix func ♭(num: Pitch) -> Pitch {
        return Pitch(hertz: num.hertz * 0.9438743126816935)
    }
}

@propertyWrapper
struct Note {
    var number: Int
    var wrappedValue: Int {
        get { number }
        set { number = newValue }
    }
    static postfix func ♯(num: Note) -> Note {
        return Note(number: num.number+1)
    }
    static postfix func ♭(num: Note) -> Note {
        return Note(number: num.number+1)
    }
}

postfix operator ♪


extension Pitch {
    // Convert a pitch to a MIDI note number
    static postfix func ♪(num: Pitch) -> Note {
        return Note(number: Int(round(12 * log2(num.wrappedValue / 440.0))))
    }
}

enum MIDI {
    enum Notes {
        // C4
        static let C = Note(number: 60)
        // D
        static let D = Note(number: 62)
        // E
        static let E = Note(number: 64)
        // F
        static let F = Note(number: 65)
        // G
        static let G = Note(number: 67)
        // A
        static let A = Note(number: 69)
        // B
        static let B = Note(number: 71)
    }
}

@resultBuilder
enum NoteBuilder{
    static func buildBlock(_ notes: Note...) -> [MIDINoteMessage] {
        notes.map {
            MIDINoteMessage(channel: 1,
                            note: UInt8($0.number),
                            velocity: 64,
                            releaseVelocity: 0,
                            duration: 1 )
        }
    }
}

func 𝄞(@NoteBuilder _ makeNotes: () ->[MIDINoteMessage])-> MusicSequence {
    var sequence : MusicSequence? = nil
    var musicSequence = NewMusicSequence(&sequence)
    
    var track : MusicTrack? = nil
    var musicTrack = MusicSequenceNewTrack(sequence!, &track)
    
    var time = MusicTimeStamp(1.0)
    
    for n in makeNotes() {
        var note = n
        musicTrack = MusicTrackNewMIDINoteEvent(track!, time, &note)
        time += 0.3
    }
    return sequence!
}

let A = MIDI.Notes.A
let B = MIDI.Notes.B
let C = MIDI.Notes.C
let D = MIDI.Notes.D
let E = MIDI.Notes.E
let F = MIDI.Notes.F
let G = MIDI.Notes.G

var musicPlayer : MusicPlayer? = nil
var player = NewMusicPlayer(&musicPlayer)

var  📲🎶 = 𝄞{
    F; C; A♯; C; F
    G; G; A♯; C; C; A♯; G; C; F; C; A♯; C; F
    G; G; A♯; C; C; A♯; G; C; F; C; A♯; C; F
    G; G; A♯; C; C; A♯; G; C; F; C; A♯; C; F
}

player = MusicPlayerSetSequence(musicPlayer!, 📲🎶)
player = MusicPlayerStart(musicPlayer!)

which gives you

I know it doesn't sound good (to be fair to me, Im trying to learn this stuff :sweat_smile:)

But it's really fun to play with (on playgrounds on macOS synths a piano sound which sounds a lot better, I don't know what instrument iPad playgrounds is modelled on)

I know it sounds gimmicky to be working on a Music DSL that resembles music notation, since there aren't any musical symbols on mechanical keyboards, this entire musical symbol business on a hardware keyboard doesn't make sense.

But iPad's keyboard is virtual and since this is equally happy on a playground on the iPad as it is on the Mac, why not take advantage of the virtual keyboard?

So the next 2 things I would like to look in to are

  • extracting this out to a package on it's own.
  • A custom keyboard for iPad with 𝄞, ♯,♭ and other musical symbols.

But the code above is quite literally the sum of all Swift code I've ever written, so there's a lot to learn.

That's on top of this thing actually was born out of me trying to teach my self music theory.

I'm sure to people who've done a fair bit of Swift this is second nature, but I have to keep checking the language guide for everything :sweat_smile: so on the one side I have the Swift Language Guide on the other side I have music theory books.

I'm learning so much from this.

Would very much appreciate any guidance or help folks may be able to offer either about swift or music theory :smile:

Hope this is helpful to someone...

10 Likes