Upgrade to Pro — share decks privately, control downloads, hide ads and more …

プログラマのための作曲入門

Avatar for CHEEBOW CHEEBOW
September 21, 2025

 プログラマのための作曲入門

プログラマのための作曲入門

iOSDC Japan 2025

2025/09/21 13:55〜
Track A

音楽とプログラミングはまったく違うもの。芸術と情報工学の間に、共通点などない。
……と思われている方も多いようです。
しかしながら、それは大きな誤解なのです。
なぜなら、音楽は数学だから!
「ド ド# レ レ# ミ ファ ファ# ソ ソ# ラ ラ# シ」、誰もが知っているこの音の並びを生み出したのは、古代ギリシャの数学者ピタゴラス、と言われています。

プログラマと音楽は相性が良いのです。
私自身も、平日はプログラマ、週末はアイドルなどへの楽曲提供をする作曲家です。

プログラミングはコード(code)を書き、作曲はコード(chord)を使います。

この「コード」を中心にプログラマなりの作曲を考えてみましょう。
コードネームから構成音を求めるプログラムを作ることができます。
コード進行にはパターンがあり、デザインパターンやライブラリのように利用することができます。
コード進行からメロディを導き出して作曲していくことができます。

本トークでは

作曲とはなんなのか
コードの仕組み
コード進行はパターンである
コード進行を演奏するiOSアプリを作る
コード進行から曲を作る
についてお話しします。

ソースコードはこちらから、どうぞ
https://gist.github.com/cheebow/ddff7928803c0f233e7b580f663e13d8
https://github.com/cheebow/ChordPlayer

最後に再生した動画はこちらから、どうぞ
https://youtu.be/3ICOEkqkp3c

Avatar for CHEEBOW

CHEEBOW

September 21, 2025
Tweet

More Decks by CHEEBOW

Other Decks in Programming

Transcript

  1. ເ຾ͶΉ(ͰΜͺ૊.inc)ɺσΟΞεςʔδΞΠυϧ෦ɺѪԵঁˑDOLLɺCOSMIC STAGEɺ DollˑElementsɺJewel Kissɺ⁊୔͋Γ͋ɺ̩ʂ̥ɺؙࢁՆླɺas f ɺࠤ໺༑ཬࢠ(ѪԵঁ ˒DOLL)ɺHoney SquashɺϋοΫΨʔϧζɺനࣛ͸Δͷɺ᷒ྦΊΔɺLuce Twinkle WinkˑɺϚϘϩγՄ࿁GeNEɺཹक൪Ψʔϧζɺϋϐυϧ

    ~ Happy Idol Project ~ ɺࠤݪඦ ԻɺAngeˑReveɺPIP: Platonics Idol PlatformɺPICK UP GIRLSʯɺCarnivalˑStarsɺ RYUKYU IDOLɺ͐͡ΔͷʂɺSˑUTHERN CROSSɺLovinˍSɺSnowRabbit feat. ླ໦Ώ ͖ɺLasRabbiɺਆ॓ɺCAMOUFLAGEɺ͓΍ΏͼϓϦϯηεɺᗅΊ͖ˑΞϯϑΥϨϯτɺ little more.ɺϕʔεϘʔϧΨʔϧζɺ෱Ӭ޾ւɺఱ੖Εʂݪ॓ɺ࣌୅Ճ଎૷ஔ@'mEɺ ChuˑOh!DollyɺKAMO͕ωΪΛ͠ΐͬͯ͘Δο!!!ɺ΋͔ΖΜͪΌΜ(RYUKYU IDOL)ɺࡈ ౻Εͳɺmi-naɺ༘رະ݁ɺͨͩͷঁͷࢠɻɺJewelˑRougeɺ៉੕ˑϑΟΦϨφʔυɺ Girls Live ProjectɺΞΫΞϊʔτɺύϐϓϖϙ͸೉͍͠ɺsommeil sommeilɺΔͳͬͪˑ ΄͠ɺ७ਮΧϑΣɾϥοςɺ੢࡙΄͠ɺେੴཧ೫ɺϓϦΞϞɺ٦͸Δ͔ɺRuka Bananaɺ QSCS eggɺΏ͍͟Β͢ɺ1ֶظͷલ൅ɺ#͓͏ͪΞΠυϧɺViViBeeɺPalette Projectɺ୩ ຑ༝ཬɺGood knight**ɺLove,Need Meὑ ‼︎ ɺۋ৫Ί͙Έɺ#ϖϯλϓϦζϜɺϫϯεΞ νϟϯεɺAALAɺTwinkle ProjectˑɺSnowˑFelizɺ#heishaɺ࿀Իܖ໿ɺ͖Έͱͷϫϯ μʔϥϯυɺະμɺ܅Ϯਪηζɻɺnemumi(sommeil sommeil)ɺύεςϧϗϩάϥϜɺγΣ ϦίϑϨɺ৓࡚౧՚ɺελʔνεͷϥϒϨλʔɺSquall Lineɺݪ॓຾຾ɺif only …ɺ૬୔ ಏɺͷʔ΀ΒΜɻɺ໷ಓઇɺѪԵঁˑDOLLʢνʔϜLʣɺͤΜ͢
  2. ຐ๏গঁˑະຬɺશྗϙδςΟϒɺ੨य़ϦΞϦςΟɺGO!! MY WISH!!ɺյͯ͠ɺ७৘ɺΞΠυϧˑະຬɺLovely DaysɺΩ ϛ͸ετʔϜɺࠂനମૢɺREADY STEADY GO!!ɺΪϡοͱSTAR!!ɺϚδ࿀ˑύϯνɺΤσϯͷԂͰɺYOU GOTTA DANCE!!ɺGO!! MY

    WISH!!(KUJIRA REMIX)ɺ೒Λݟ͔ͨɺEternal Summerɺ൵͍͠my true loveɺWheel of Fortuneɺ ιϨΠϢͷຐ๏ɺKiss x KissɺϩʔΞ΢τɺϚʔϝΠυɺ͓͑ͯ͠ʂ࿀ͷϓϩτίϧɺVanillaɺͨΊଉίοτϯΩϟϯ σΟʔɺLuce Twinkle Winkˑ overtureɺႢಹϋϨʔγϣϯɺ͖Β͍ɺ͖Β͍ɺ͍͖ͩ͢ɻɺཹक൪Ψʔϧζˑኯɺ PaˑPaˑPaˑPartyɺ੺ಓখொυΩοɺWake me up!!ɺGrow Up!!ɺ͖ͬͱ͙ͬͱαϚʔσΠζɺΩϛ͸ϊΠόϥɺগঁͨ ͪͷඍ೤ɺShooting x 3ɺMagical WonderlandɺWinding RoadɺYou Go Girl!!ɺ๻ΒʹਅՆ͕͘ΔɺϓϦʔζɾϓϦʔ ζɺ͋͟΍͔ͳੈքɺ΍Β͔͍͸͊ͱɺUP TO YOU!!ɺDanger Danger!!ɺLast RabbitɺΦʔϓχϯάSEɺυΪϚΪ Trick or Treat?ɺΦʔϓχϯάSEɺ৘೤ͷBlizzardɺݬӨ˒ΪϟϥΫςΟΧɺྲྀ੕ˑϩϚϯςΟΧɺGimme a Spark!ɺϐϦΦυ Λ๊͖͠Ίͯɺίίϩ×ΫϩεήʔϜɺ͔ͬ͠Γͯ͠ΑɺΧϦΩϡϨʔλʔɺಧ͚ɺ܅ʹɺOur MusicɺѪυϧɺ PreciousˑSummerɺര྾ʂ࣬૸ʂ࿀ηϤԵঁɺਅՆͷύϦϥɺॠؒɺϑϩϨηϯτɺΞΧωΠϩɺϠμοʂɺΞΠΦϥΠ τɺ௒ઈՄ࿁ʂຐ๏গঁ΋͔ΖΜͪΌΜ͸ઃఆ্14ࡀɺLuce Twinkle Winkˑ overture 2ɺγϦΞϧɾϥόʔɺઈ๬ϩϯϦ ωεɺͳͷ͸ͳΠϯτωʔγϣϯɺͪΌ͓ɺՆͷׂ࣌ؒɺBreakthrough!!ɺ࿀ܬɺStep by StepʂɺOvertureɺ PuroˑUn fi oreɺ༿ࡩͷ໦࿙Ε೔ʹɺGoodbye Rainɺଧ্ͪ͛ՖՐɺڭ͑ͯTristarɺߦ͋ͯ͘ͷͳ͍νϣίϨʔτɺ#य़͘ Δɺ௅ൃSel fi shɺγϟχϜχɺDon't you?ɺྲྀ੕ΤτϥϯθɺγʔαΠυϥΠφʔɺӕ͖ͭΞϯεϦ΢ϜɺϨΠςϯϨʔυɺ You are my answer!!ɺΦʔϓχϯάۂɺύεςϧͷ์՝ޙɺ͓͞ΉͷATMɺॳ࿀ˑHow to My Loveɺʮ͜Ε͔Βʯɺϓϩ ᇋϥϨϠʔɺSTARTɺ܅ͱӍͷΞΫΞϦ΢Ϝɺ͍ͬ͡ͺΓDECISION. -ܾஅ-ɺఱମ஍ਤɺ͞ΑͳΒ࿡ՖɺͲΓʔΈΜˑΊ͍ ͲΓʔΈΜɺ͔͍͔͍ͨͨɺଔۀ͙Β͡Ύ͑ʔ͠ΐΜɺΦʔϓχϯάSEɺΩϥΩϥΩϥϦˑɺະདྷͷ͖ͭͮɺ͖ͬͱɺΞΠ ͩͶɺϢϝΠζϜɺΞΠγϯάɾΫοΩʔɺΦʔϓχϯάSEɺݪ৭ϘϧςʔδɺSEɺBlue Rose, Blueɺ͘͞ΒΧʔϖοτɺ ͖ΈͷͱͳΓɺGroovin' CrazyɺελʔϚΠϯɺ઴ۙઢϝϩσΟΞεɺSEɺરޫϦϑϨΫγϣϯɺϦɾϦɾϦɾϦϝϯόʔɺ ͋ͳ͕ͨҭͯͨՖɺγϟϘϯۄʹͳΕʂɺSEɺಌጦͷΧϊʔϓεɺ͍ͪ͝ϛϯτͷՆɺΞϫΧϯύχʔɺ࿀ԻˑϩϚϯαʔɺ ͱͬͽΜ͠ΌΜͷCHUˑɺWhat's up? ਓੜɺͶΉΈͷ৿ɺ೒ͷͬ͠ΆΛ͔ͭΉ·Ͱɺྠ㕩ͷεʔύʔϊϰΝɺγϟϥϥɺSEɺ ڭՊॻΛ໨Ӆ͠ʹͯ͠ɺSEɺHappy Merry-Go-RoundɺλΠϜϚγϯ͸͍Βͳ͍ɺӕͱϦΞϦςΟɺSEɺIDOLὑΤϞˠγϣ ϯɺະ֬ఆϑϩʔςΟϯάɺSnow dropɺAlchuὑΞϧίʔϧɺϥΠഴͷ֑Λͱͼ͑ͯ͜ɺԵঁͷΫϩχΫϧɺ໿ଋͷγφε λδΞ
  3. C

  4. C = υ D = Ϩ E = ϛ F

    = ϑΝ G = ι A = ϥ B = γ υϨϛϑΝͷӳޠ໊
  5. C

  6. C D E F G A B C# Db D#

    Eb F# Gb G# Ab A# Bb C D E F G A B C# Db D# Eb F# Gb G# Ab A# Bb C
  7. C D E F G A B C# Db D#

    Eb F# Gb G# Ab A# Bb C D E F G A B C# Db D# Eb F# Gb G# Ab A# Bb C 1 2 3 4
  8. C D E F G A B C# Db D#

    Eb F# Gb G# Ab A# Bb C D E F G A B C# Db D# Eb F# Gb G# Ab A# Bb C 1 2 3 4 1 2 3
  9. C D E F G A B C# Db D#

    Eb F# G# Ab A# Bb C D E F G A B C# D# Eb F# Gb G# Ab A# Bb F#m7
  10. C D E F G A B C# Db D#

    Eb F# G# Ab A# Bb C D E F G A B C# D# Eb F# Gb G# Ab A# Bb F#m7 1 2 3
  11. C D E F G A B C# Db D#

    Eb F# G# Ab A# Bb C D E F G A B C# D# Eb F# Gb G# Ab A# Bb F#m7 4 1 2 3 1 2 3
  12. C D E F G A B C# Db D#

    Eb F# G# Ab A# Bb C D E F G A B C# D# Eb F# Gb G# Ab A# Bb F#m7 1 2 3 4 1 2 3 1 2 3
  13. C D E F G A B C# Db D#

    Eb G# Ab A# Bb C D E F G A B D# Eb F# Gb G# Ab A# Bb Am7 1 2 3 4 1 2 3 1 2 3 F# Gb C# Db
  14. F#m7 F# A C# 3 4 E 3 A C

    E 3 4 G 3 Am7
  15. 4 - 3 3 - 4 4 - 3 -

    3 4 - 3 - 4 3 - 4 - 3 5 - 2 3 - 3 - 3 4 - 4 3 - 3 - 4 X Xm X7 XM7 Xm7 Xsus4 Xdim Xaug Xm7b5 ίʔυ໊ͱԻͷੵΈํ
  16. import Foundation let noteToSemitone: [String: Int] = [ "C": 0,

    "C#": 1, "Db": 1, "D": 2, "D#": 3, "Eb": 3, "E": 4, "Fb": 4, "E#": 5, "F": 5, "F#": 6, "Gb": 6, "G": 7, "G#": 8, "Ab": 8, "A": 9, "A#": 10, "Bb": 10, "B": 11, "Cb": 11, "B#": 0 ] let semitoneToNote: [Int: (sharp: String, flat: String)] = [ 0: ("C", "C"), 1: ("C#", "Db"), 2: ("D", "D"), 3: ("D#", "Eb"), 4: ("E", "Fb"), 5: ("F", "F"), 6: ("F#", "Gb"), 7: ("G", "G"), 8: ("G#", "Ab"), 9: ("A", "A"), 10: ("A#", "Bb"), 11: ("B", "Cb") ] func chordNotes(chord: String) -> [String] { let root = String(chord.prefix(while: { ["C", "D", "E", "F", "G", "A", "B"].contains($0) || $0 == "#" || $0 == "b" })) let suffix = String(chord.dropFirst(root.count)) guard let rootSemitone = noteToSemitone[root] else { return [] } var intervals = switch suffix { case "m" : [0, 3, 7] case "7" : [0, 4, 7, 10] case "m7" : [0, 3, 7, 10] case "M7" : [0, 4, 7, 11] case "add9" : [0, 4, 7, 14] case "madd9": [0, 3, 7, 14] case "dim" : [0, 3, 6] case "aug" : [0, 4, 8] case "m7b5" : [0, 3, 6, 10] default : [0, 4, 7] } let notes = intervals.map { let semitone = ($0 + rootSemitone) % 12 return root.contains("#") ? semitoneToNote[semitone]!.sharp : semitoneToNote[semitone]!.flat } return notes }
  17. // chord:”G#m7” -> “G#” let root = String(chord.prefix(while: { ["C",

    "D", "E", "F", "G", “A", "B"].contains($0) || $0 == "#" || $0 == "b" })) // chord:”G#m7” -> “m7” let suffix = String(chord.dropFirst(root.count)) ίʔυ໊͔ΒɺϧʔτԻͱίʔυͷछྨΛऔಘͯ͠
  18. guard let rootSemitone = noteToSemitone[root] else { return [] }

    var intervals = switch suffix { case "m" : [0, 3, 7] ……… default : [0, 4, 7] } let notes = intervals.map { let semitone = ($0 + rootSemitone) % 12 return root.contains("#") ? semitoneToNote[semitone]!.sharp : semitoneToNote[semitone]!.flat } ϧʔτ͔Βͷڑ཭Ͱίʔυͷߏ੒ԻΛ୳͠·͢
  19. // root:”G#” -> rootSemitone:8 guard let rootSemitone = noteToSemitone[root] else

    { return [] } let noteToSemitone: [String: Int] = [ "C": 0, "C#": 1, "Db": 1, "D": 2, "D#": 3, "Eb": 3, "E": 4, "Fb": 4, "E#": 5, "F": 5, "F#": 6, "Gb": 6, "G": 7, "G#": 8, "Ab": 8, "A": 9, "A#": 10, "Bb": 10, "B": 11, "Cb": 11, "B#": 0 ]
  20. // suffix:”m7” -> intervals:[0, 3, 7, 10] var intervals =

    switch suffix { case "m" : [0, 3, 7] case "7" : [0, 4, 7, 10] case "m7" : [0, 3, 7, 10] case “M7" : [0, 4, 7, 11] case "add9" : [0, 4, 7, 14] case "madd9": [0, 3, 7, 14] case "dim" : [0, 3, 6] case "aug" : [0, 4, 8] case "m7b5" : [0, 3, 6, 10] default : [0, 4, 7] }
  21. // root:”G#” // rootSemitone:8 // intervals:[0, 3, 7, 10] let

    notes = intervals.map { // semitone: 8, 11, 3, 6 let semitone = ($0 + rootSemitone) % 12 return root.contains("#") ? semitoneToNote[semitone]!.sharp : semitoneToNote[semitone]!.flat }
  22. // root:”G#” // rootSemitone:8 // intervals:[0, 3, 7, 10] let

    notes = intervals.map { // semitone: 8, 11, 3, 6 return root.contains("#") ? semitoneToNote[semitone]!.sharp : semitoneToNote[semitone]!.flat } // notes:[“G#”, “B”, “D#”, “F#”] let semitoneToNote: [Int: (sharp: String, flat: String)] = [ 3: ("D#", "Eb"), 6: ("F#", "Gb"), 8: ("G#", "Ab"), 11: ("B", "Cb") ]
  23. Ԧಓਐߦ • Subtitle / Of fi cial඘உdiss • ॕ෱ /

    YOASOBI • ՄѪͯ͘͝ΊΜ feat.ͪΎʔͨΜ / Honeyworks • ͪΎɺଟ༷ੑɻ / ano • ܅͸ϩοΫΛௌ͔ͳ͍ / ͍͋ΈΐΜ
  24. Χϊϯਐߦ • ΫϦεϚεɾΠϒ / ࢁԼୡ࿠ • ෛ͚ͳ͍Ͱ / ZARD •

    ͘͞ΒΜ΅ / େ௩Ѫ • Θ͕··δϡϦΤοτ / BOOWY • গ೥࣌୅ / Ҫ্ཅਫ • Ѫ͸উͭ / KAN
  25. ؙαਐߦ • ؙϊ಺ασΟεςΟοΫ / ௣໊ྛޝ • ໷ʹۦ͚Δ / YOASOBI •

    άοόΠએݴ / Chinozo • ѪΛ఻͍͑ͨͩͱ͔ / ͍͋ΈΐΜ • ඵ਑ΛטΉ / ͣͬͱਅ໷தͰ͍͍ͷʹɻ
  26. খࣨਐߦ • Get Wild / TM NETWORK • ϑϥΠϯάήοτ /

    AKB48 • ന೔ / King Gnu • ๻Β͸ࠓͷͳ͔Ͱ / μ's • ઍຊࡩ / ࠇ͏͞P
  27. C

  28. C/E

  29. C:4

  30. // ϦζϜه๏Λ෼཭ͯ͠ύʔε let (chordNameWithBass, rhythmNotation, duration) = Self.parseNotation(chordNotation) // ϕʔεԻΛղੳʢϕʔεԻ໊΋ฦ͢ʣ

    let (chordName, specifiedBassNote, bassNoteName) = Self.parseBassNote(from: chordNameWithBass) // ϧʔτԻͱαϑΟοΫεΛղੳ guard let (rootNote, rootName, suffix) = Self.parseRootAndSuffix(from: chordName) else { return nil }
  31. /// ίʔυαϑΟοΫε͔ΒԻఔʢྦྷੵ஋ʣΛऔಘ internal static func intervals(for suffix: String) -> [Int]?

    { switch suffix { case "", "M": return [0, 4, 7] // Major case "m": return [0, 3, 7] // Minor case "7": return [0, 4, 7, 10] // Dominant 7th case "m7": return [0, 3, 7, 10] // Minor 7th case "M7": return [0, 4, 7, 11] // Major 7th case "m7b5": return [0, 3, 6, 10] // Half-diminished case "dim7": return [0, 3, 6, 9] // Diminished 7th case "sus4": return [0, 5, 7] // Suspended 4th case "add9": return [0, 4, 7, 14] // Add 9th case "madd9": return [0, 3, 7, 14] // Minor add 9th case "6": return [0, 4, 7, 9] // 6th case "m6": return [0, 3, 7, 9] // Minor 6th case "9": return [0, 4, 7, 10, 14] // 9th case "m9": return [0, 3, 7, 10, 14] // Minor 9th case "aug": return [0, 4, 8] // Augmented case "dim": return [0, 3, 6] // Diminished default: return nil } }
  32. // ֤ߏ੒ԻΛੜ੒ͯ͠ϛοΫε for (index, (frequency, volume)) in noteData.enumerated() { //

    Ճࢉ߹੒ʹΑΔ೾ܗੜ੒ʢجԻ + ഒԻͰϐΞϊԻ৭Λ໛฿ʣ let fundamental = sin(2.0 * .pi * phases[index]) let harmonic2 = sin(2.0 * .pi * phases[index] * 2.0) * 0.5 let harmonic3 = sin(2.0 * .pi * phases[index] * 3.0) * 0.3 let harmonic4 = sin(2.0 * .pi * phases[index] * 4.0) * 0.2 let harmonic5 = sin(2.0 * .pi * phases[index] * 5.0) * 0.1 let waveform = fundamental + harmonic2 + harmonic3 + harmonic4 + harmonic5 // ADSRΤϯϕϩʔϓͷܭࢉ var envelope: Double = 0.0 if time < attack { envelope = time / attack } else if time < attack + decay { let decayProgress = (time - attack) / decay envelope = 1.0 - (1.0 - sustain) * decayProgress } else { let sustainTime = time - attack - decay envelope = sustain * exp(-sustainTime * Config.release) } let sample = Float(waveform * envelope) * Config.waveformAmplitude * volume mixedSample += sample phases[index] += frequency / sampleRate if phases[index] > 1.0 { phases[index] -= 1.0 } }