Хотел бы показать, как быстра может быть эволюция от идеи до готового кода с небездумным применением искусственного интеллекта (ИИ).
По работе у меня в road map (это, кто пока не в курсе, дорожная карта дел на год)
-
освоение ИИ в целях оптимизации тестирования;
-
написание автотестов в проекте на Kotlin.
Надо бы в этом всём прокачаться. Надо-надо… Надо подкачаться, вот и решил написать код на Kotlin с консультированием в текстовом чате с ИИ.
Давно я чаял идею написать утилиту для создания музыки, т.к. уж 20 лет как ею занимаюсь и ей обучаюсь любительски то тут, то там.
Любая музыка – суть последовательность аккордов. Если глубже, то последовательность функциональных ступеней в разных комбинациях, подчинённых законам муз.гармонии, описанных уже давно Хуго Риманом и уточнённых его последователями, но мы не будем в этот раз слишком глубоко нырять в гармонию).
Нам пока достаточно того, что упрощённо музыка – последовательность аккордов. Стало быть, чтобы создать музыку нужно эту последовательность создать = сгенерировать.
Спойлер:
Всё получилось. Утилиту сможете легко применить в вашем браузере. В статье описал как. Если чешутся руки погенерить аккорды мотайте сразу вниз на раздел “Как запустить утилиту?“! В три простых действия получите аккорды и удовольствие :)
Первый блин.com
Сперва я составил промпт для текстового ИИ-ассистента (Qwen):
Помоги в простом формате написать программу на Kotlin, которая:
Запрашивает и принимает у пользователя набор аккордов (string-слов) через пробел.
Если пользователь не задаёт, по дефолту используем базовую белоклавишную тональность ля-минор/до-мажор (именно в ней те самые “блатные аккорды”):
Am Bdim С Dm Em F G.Уточняет сколько аккордов должно быть в последовательности: 2, 3, 4 или больше (по дефолту – 3)
Выдаёт как финальный результат рандомную последовательность из предложенных пользователем аккордов.
Предлагает повторить сценарий.
ИИ сгенерил код:
import kotlin.random.Random
fun main() {
while (true) {
// 1. Получаем аккорды от пользователя
println("Введите аккорды через пробел (или нажмите Enter для использования по умолчанию):")
val input = readLine()
val chords = if (input.isNullOrBlank()) {
listOf("Am", "Bdim", "C", "Dm", "Em", "F", "G")
} else {
input.split(" ").filter { it.isNotEmpty() }
}
if (chords.isEmpty()) {
println("Нет аккордов для обработки. Попробуйте снова.")
continue
}
// 2. Получаем количество аккордов в последовательности
println("Сколько аккордов в последовательности? (2, 3, 4 или больше, по умолчанию 3):")
val sequenceLength = try {
val lengthInput = readLine()
when {
lengthInput.isNullOrBlank() -> 3
else -> lengthInput.toIntOrNull() ?: 3
}
} catch (e: NumberFormatException) {
3
}
// 3. Генерируем случайную последовательность
val randomSequence = (1..sequenceLength).map {
chords[Random.nextInt(chords.size)]
}
// 4. Выводим результат
println("Случайная последовательность аккордов: ${randomSequence.joinToString(" ")}")
// 5. Спрашиваем о повторении
println("Хотите повторить? (y/n):")
val repeat = readLine()?.lowercase()
if (repeat != "y" && repeat != "yes") {
break
}
}
println("Спасибо за интерес к созданию музыки! Впереди всегда много интересного.")
}
Поскольку сначала хотелось прогнать код в браузере онлайн, а не в IDE (среде разработки), попробовал исполнить его в https://play.kotlinlang.org/. Взаимодействия через текстовый диалог, как я задумал, со мной не произошло: этот онлайн-компилятор не рассчитан на вводы пользователя. Зато сработал прописанный в коде механизм предварительно заданных (=дефолтных) значений. Выполнена цель генерации рандомных аккордов из “умолчательного” набора тональности ля-минор.
При уточнении в чате с ИИ, стало ясно, что дело в ограниченности исполнения кода в “песочнице” (play.kotlinlang.org/). ИИ предложил в этом онлайн-компиляторе исполнить упрощённый код:
import kotlin.random.Random
fun main() {
// Используем стандартный набор аккордов
val defaultChords = listOf("Am", "Bdim", "C", "Dm", "Em", "F", "G")
val chords = defaultChords
// Задаем длину последовательности
val sequenceLength = 3
// Генерируем случайную последовательность
val randomSequence = (1..sequenceLength).map {
chords[Random.nextInt(chords.size)]
}
// Выводим результат
println("Случайная последовательность аккордов: ${randomSequence.joinToString(" ")}")
println("Спасибо за неподдельное любопытство в создании музыки!")
}
Таким образом, вручную меняя значения в функции listOf из инициатора для значения defaultChords, можно без проблем получить желаемую случайную последовательность аккордов средствами Kotlin. Число аккордов в последовательности можно регулировать через значение sequenceLength.
Код получился лаконичный и интуитивно понятный, за что и любят язык программирования Kotlin. Однако в целом заставлять вчитываться в код – это не очень-то дружелюбно к пользователю, если он не знаком с программированием или алгоритмами. Не всем своим приятелям-музыкантам я могу дать в пользование такую утилиту. Вместе с ней нужно высылать пояснительные бригады, а их не напасёшься.
Я захотел сделать что-то более универсальное, гибкое и дружелюбное.
И кстати… откуда я взял изначальные аккорды? Почему именно такие? Гитаристы знают, что есть ещё и другие …
Можно масштабировать этот мой первый опыт для всех тональностей.
Утилита для любой тональности
Ах да, тональностей :D Упоминаю в седьмой раз тональность, но не расшифровываю, что ж…
Тональность – это (опять же упрощённо) набор из 7 нот. Наш с вами друг Иоган Себастьян Бах выбирал определённые 7 нот из 12 нот хроматического ряда. Да-да, нот далеко не 7! 😉 В каждой из тональностей он написал лютейшую красоту, разобранную музыкантами-наследниками на цитаты (см. и слушай “Хорошо темперированный клавир”).
7 нот тональности группируются в эдакие комплексы – трезвучия или всем известные аккорды. Мы в этом маленьком исследовании будем строить базовые трезвучия. Базовых трезвучий, ожидаемо для великих комбинаторов, – 7. Строятся они от каждой из нот по терциям (то есть упрощённо через одну белую клавишу на фортепиано). Короче говоря, в каждой тональности по 7 базовых аккордов.
Собственно таблица тональностей, с которой пользователь может легко и удобно взаимодействовать через простой ввод числа и буков, и является самым большим достижением моего диалога с ИИ.
Те, кто прошёл музыкальную школу жизни отметят, что таблица упорядочена по родственным тональностям, имеющим одинаковое число знаков при ключе (♯/♭), что достаточно удобненько само по себе.
Как это получилось?
В чате у ИИ я попросил дать возможность пошагово сгенерировать последовательность аккордов:
Составить таблицу тональностей, и словарь их аккордов.
Дать пользователю возможность выбирать тональность и получить набор её базовых аккордов.
Выбрать аккорды и задать число их случайных появлений в последовательности.
Пользователь имеет возможность повторить ключевые шаги программы, то есть пересоздать последовательность аккордов по-другому.
Тут я включу skip и перемотаю мои мытарства в диалоге с юным ИИ (я, кстати, сменил Qwen на Perplexity), не особо грамотным в муз.теории, а также в тестировании и отладке программы. Покажу вам сразу самую мякотку – готовую программу, которую можно успешно запустить, задав все нужные параметры на https://www.codechef.com/kotlin-online-compiler:
fun main() {
val app = ChordSequenceGenerator()
app.run()
}
class ChordSequenceGenerator {
private enum class RepeatMode {
SAME_CHORDS,
SAME_KEY_NEW_CHORDS,
NEW_KEY,
EXIT
}
fun run() {
println(
"=== Генератор последовательностей аккордов ===n" +
"В каждой тональности есть по 7 аккордов, связанных музыкльной гармонией.n" +
"Давайте сгенерируем случайную последовательность аккордов в любой из тональностей!" +
"*-dur - мажорные тональности, *-moll - минорныеn"
)
while (true) {
try {
val key = step1_chooseKey()
var selectedChords = step2_chooseChords(key)
var count = step3_askCount()
while (true) {
val sequence = generateSequence(selectedChords, count)
displaySequence(sequence,key)
when (step4_askRepeatMode()) {
RepeatMode.SAME_CHORDS -> {
continue
}
RepeatMode.SAME_KEY_NEW_CHORDS -> {
selectedChords = step2_chooseChords(key)
count = step3_askCount()
}
RepeatMode.NEW_KEY -> {
break
}
RepeatMode.EXIT -> {
println("Спасибо за использование!")
return
}
}
}
} catch (e: Exception) {
println("Ошибка: ${e.message}")
if (!step5_askRepeatAfterError()) break
println()
}
}
println("Спасибо за использование!")
}
private fun step1_chooseKey(): String {
println("Шаг 1: Выбор тональности")
println()
println(" № | Мажор (M) | минор (m) | Знаки альтерации")
println("------|-----------------|-----------------|-----------------")
val majors = mutableListOf<String>()
val minors = mutableListOf<String>()
Vocabulary.keyPairs.forEachIndexed { idx, (maj, min) ->
majors.add(maj)
minors.add(min)
val sigMaj = Vocabulary.keySignatures[maj] ?: "?"
val numMaj = idx + 1
println(
"${numMaj.toString().padStart(5)} | ${maj.padEnd(15)} | " +
"${min.padEnd(15)} | $sigMaj"
)
}
print("nВведите номер строки: ")
val choice = readLine()?.toIntOrNull()
if (choice != null && choice in 1..majors.size) {
val index = choice - 1
print("Мажор (M) или минор (m): ")
val type = readLine()?.lowercase()?.trim()
return if (type == "m" || type == "минор" || type == "moll") {
minors[index]
} else {
majors[index]
}
}
println("Неверный выбор, используется C-dur")
return "C-dur"
}
private fun step2_chooseChords(key: String): List<String> {
val availableChords = Vocabulary.allChords[key]
?: error("Неизвестная тональность: $key")
println("nШаг 2: Доступные аккорды выбранной тональности: $key")
availableChords.forEachIndexed { index, chord ->
println("${index + 1}. $chord")
}
print("Введите номера аккордов через пробел (Enter для всех): ")
val input = readLine()?.trim()
if (input.isNullOrBlank()) {
return availableChords
}
val selected = mutableListOf<String>()
input.split("\s+".toRegex())
.forEach { num ->
val index = num.toIntOrNull()
if (index != null && index in 1..availableChords.size) {
selected.add(availableChords[index - 1])
}
}
return if (selected.isEmpty()) availableChords else selected.distinct()
}
private fun step3_askCount(): Int {
println("nШаг 3: количество аккордов в последовательности")
print("Сколько аккордов в последовательности? (по умолчанию 4): ")
val input = readLine()?.trim()
return input?.toIntOrNull() ?: 4
}
private fun generateSequence(chords: List<String>, count: Int): List<String> {
return (1..count).map { chords.random() }
}
private fun displaySequence(sequence: List<String>, key: String) {
println("n=== Последовательность в $key, которую мы заслужили ===")
sequence.chunked(4).forEach { chunk ->
println(chunk.joinToString(" "))
}
}
private fun step4_askRepeatMode(): RepeatMode {
println("nЕщё?")
println("1 — сгенерировать ещё раз с теми же аккордами")
println("2 — выбрать другие аккорды в той же тональности")
println("3 — выбрать новую тональность")
println("4 — выйти")
print("Ваш выбор: ")
return when (readLine()?.trim()) {
"1" -> RepeatMode.SAME_CHORDS
"2" -> RepeatMode.SAME_KEY_NEW_CHORDS
"3" -> RepeatMode.NEW_KEY
"4" -> RepeatMode.EXIT
else -> RepeatMode.EXIT
}
}
private fun step5_askRepeatAfterError(): Boolean {
println("nХотите попробовать снова? (y/n): ")
val input = readLine()?.lowercase()?.trim()
return input == "y" || input == "yes" || input == "д" || input == "да"
}
}
object Vocabulary {
val keySignatures = mapOf(
// мажоры
"C-dur" to "0",
"G-dur" to "1♯",
"D-dur" to "2♯",
"A-dur" to "3♯",
"E-dur" to "4♯",
"B-dur" to "5♯",
"C♭-dur" to "7♭",
"F♯-dur" to "6♯",
"G♭-dur" to "6♭",
"D♭-dur" to "5♭",
"C♯-dur" to "7♯",
"A♭-dur" to "4♭",
"E♭-dur" to "3♭",
"B♭-dur" to "2♭",
"F-dur" to "1♭",
// миноры
"A-moll" to "0",
"E-moll" to "1♯",
"B-moll" to "2♯",
"F♯-moll" to "3♯",
"C♯-moll" to "4♯",
"G♯-moll" to "5♯",
"A♭-moll" to "7♭",
"E♭-moll" to "6♭",
"D♯-moll" to "6♯",
"B♭-moll" to "5♭",
"A♯-moll" to "7♯",
"F-moll" to "4♭",
"C-moll" to "3♭",
"G-moll" to "2♭",
"D-moll" to "1♭"
)
val keyPairs = listOf(
"C-dur" to "A-moll",
"G-dur" to "E-moll",
"D-dur" to "B-moll",
"A-dur" to "F♯-moll",
"E-dur" to "C♯-moll",
"B-dur" to "G♯-moll",
"C♭-dur" to "A♭-moll",
"G♭-dur" to "E♭-moll",
"F♯-dur" to "D♯-moll",
"C♯-dur" to "A♯-moll",
"D♭-dur" to "B♭-moll",
"A♭-dur" to "F-moll",
"E♭-dur" to "C-moll",
"B♭-dur" to "G-moll",
"F-dur" to "D-moll"
)
val allChords = mapOf(
"C-dur" to listOf("C", "Dm", "Em", "F", "G", "Am", "B°"),
"G-dur" to listOf("G", "Am", "Bm", "C", "D", "Em", "F♯°"),
"D-dur" to listOf("D", "Em", "F♯m", "G", "A", "Bm", "C♯°"),
"A-dur" to listOf("A", "Bm", "C♯m", "D", "E", "F♯m", "G♯°"),
"E-dur" to listOf("E", "F♯m", "G♯m", "A", "B", "C♯m", "D♯°"),
"B-dur" to listOf("B", "C♯m", "D♯m", "E", "F♯", "G♯m", "A♯°"),
"F♯-dur" to listOf("F♯", "G♯m", "A♯m", "B", "C♯", "D♯m", "E♯°"),
"C♯-dur" to listOf("C♯", "D♯m", "E♯m", "F♯", "G♯", "A♯m", "B♯°"),
"C♭-dur" to listOf("C♭", "D♭m", "E♭m", "F♭", "G♭", "A♭m", "B♭°"),
"G♭-dur" to listOf("G♭", "A♭m", "B♭m", "C♭", "D♭", "E♭m", "F°"),
"D♭-dur" to listOf("D♭", "E♭m", "Fm", "G♭", "A♭", "B♭m", "C°"),
"A♭-dur" to listOf("A♭", "B♭m", "Cm", "D♭", "E♭", "Fm", "G°"),
"E♭-dur" to listOf("E♭", "Fm", "Gm", "A♭", "B♭", "Cm", "D°"),
"B♭-dur" to listOf("B♭", "Cm", "Dm", "E♭", "F", "Gm", "A°"),
"F-dur" to listOf("F", "Gm", "Am", "B♭", "C", "Dm", "E°"),
"A-moll" to listOf("Am", "B°", "C", "Dm", "Em", "F", "G"),
"E-moll" to listOf("Em", "F♯°", "G", "Am", "Bm", "C", "D"),
"B-moll" to listOf("Bm", "C♯°", "D", "Em", "F♯m", "G", "A"),
"F♯-moll" to listOf("F♯m", "G♯°", "A", "Bm", "C♯m", "D", "E"),
"C♯-moll" to listOf("C♯m", "D♯°", "E", "F♯m", "G♯m", "A", "B"),
"G♭-moll" to listOf("G♭m", "A♭°", "B♭", "C♭m", "D♭m", "E♭", "F"),
"D♭-moll" to listOf("D♭m", "E♭°", "F", "G♭m", "A♭m", "B♭", "C"),
"A♭-moll" to listOf("A♭m", "B♭°", "C♭", "D♭m", "E♭m", "F", "G"),
"E♭-moll" to listOf("E♭m", "F°", "G♭", "A♭m", "B♭m", "C", "D"),
"D♯-moll" to listOf("D♯m", "E♯°", "F♯", "G♯m", "A♯m", "B♭", "C♯"),
"B♭-moll" to listOf("B♭m", "C°", "D♭", "E♭m", "Fm", "G", "A"),
"A♯-moll" to listOf("A♯m", "B♯°", "C♯", "D♯m", "E♯m", "F♯", "G♯"),
"F-moll" to listOf("Fm", "G°", "A♭", "B♭m", "Cm", "D", "E"),
"C-moll" to listOf("Cm", "D°", "E♭", "Fm", "Gm", "A♭", "B♭"),
"G-moll" to listOf("Gm", "A°", "B♭", "Cm", "Dm", "E♭", "F"),
"D-moll" to listOf("Dm", "E°", "F", "Gm", "Am", "B♭", "C")
)
}
Как запустить утилиту?
Программу выше можно скопировать и вставить в левое поле интерфейса codechef (1), …

Параметры для запуска можно задать в правом окне (2): каждой параметр (13, m и т.д.) на отдельной строке представляют собой ответы на вопросы из программы, где строки пронумерованы по порядку ввода:
Шаг 1: Выбор тональности:
-
Введите номер строки:
в примере 13 -
Мажор (M) или минор (m):
в примере m
(если ничего не задать, оставив строку пустой будет Мажор)Шаг 2: Доступные аккорды выбранной тональности:
-
Введите номера аккордов через пробел (Enter для всех):
в примере 1 2 3 4 5 6 7 (но лучше для минора 2-ую ступень не выбирать ;) )
(если ничего не задать, оставив строку пустой будут все 7 аккордов)Шаг 3: количество аккордов в последовательности
-
Сколько аккордов в последовательности? (по умолчанию 4):
в примере ничего не задано, просто по Enter оставил строку пустой. -
Дальше блок повторных вводов: там пока имеет смысл вводить только повторные генерации в той же тональности через ввод “1”. Я 3 раза ввёл “1” в своём примере и получил 3 последовательности.
Входные параметры (Input) для взаимодействия с программой.
Итак, последовательности в выбранной мной тональности, которые я заслужил.
=== Последовательность в C-moll, которую мы заслужили ===
E♭ Fm E♭ Cm
Ещё?
1 — сгенерировать ещё раз с теми же аккордами
2 — выбрать другие аккорды в той же тональности
3 — выбрать новую тональность
4 — выйти
...
=== Последовательность в C-moll, которую мы заслужили ===
B♭ Cm Gm E♭
...
=== Последовательность в C-moll, которую мы заслужили ===
E♭ A♭ E♭ Gm
Умудрённые музыканты увидят, что первую из последовательностей утилита даже кононично закончила на тоническом трезвучии (T). Совпадение? В этом случае точно – да!
Попробовал сыграть все три последовательности на гитаре. Всё звучит классно! 🤘 🤘 🤘
Можно развивать последовательности, меняя ритмику, длительности игры аккорда и даже сам аккорд: был Xm – а стал Xm7 или Xm maj7 или Xsus2/4 или Xm9 или Xaug и т.д.).
Диезы и бемоли не пугают прошаренных гитаристов, ведь последние знают, что:
-
аппликатуры гитарного аккорда с бемолем (♭) левее на 1 лад своего “обычного” представления на грифе:
например, E♭ ← E. -
аппликатуры с диезом (♯) – правее на 1 лад:
например, Em → E♯m.
Пути развития утилиты
-
Надо в github такое оформлять, знаю. Пока ещё не сделал, ибо новичок в создании кода с нуля. Ещё бы и красиво портянку кода на классы разделить. Когда сделаю, обновлю эту статью.
-
Можно добавить и табулатуры аккордов, и иные представления аккордов, но это, пожалуй, в отдельном этапе.
-
Хотел подарить вам эту утилиту в виде Telegram-бота. Научился разворачивать, подтягиваю зависимости либы com.github.kotlintelegrambot% через gradle и даже запустил бота с токеном от @BotFather из Телеграм, но, увы, сейчас Телега движется туго, надо как-то через прокси или не с российским IP разворачивать удалённо на сервере, поэтому реализация через ТГ-бот отложена.
Бот в Макс только через ЮЛ, у меня только самозанятость оформлена пока.
Как использовать
-
Индивидуальная практика в музыке. На гитаре, укулеле или клавишах сыграйте все аккорды – получите опыт и навык и хвастайтесь друзьям ачивкой!
-
Практика в коллективе. Сыграйте с вашим рок-бэндом, докрутите последовательность, как вам надо – и играйте новую песни с вашими аккордами, ведь мне они не принадлежат, музыка – общее достояние.
-
Можете “докрутить” сам код утилиты и сделать вашу утилиту, которая, например,
-
будет генерировать последовательности по строгим законам гармонии (T-S-D) или допускать, как в блюзе нарушения строгости (T-D-S),
-
будет давать некие отклонения или модуляции из тональности в тональность.
-
Будет транспонировать цепочку сгенерированных аккордов (=переводить в другую тональность)
-
… Ещё масса вариантов. Делитесь вашими идеями, и успехов их в реализации!
-
Желаю интересных генераций, а также хорошей и – главное – качественной музыки!
Дмитрий Исанин
Тестировщик ПО, сонграйтер и исполнитель, изучаю и применяю ИТ и ИИ для создания песен и музыки, а также озвучки
Про то, как делаю музыку в последние 2 года читайте статьи на Хабре:
https://habr.com/ru/users/Dmitry_Isanin/articles/,-
среди которых “Почему я выбрал Suno AI для создания мемного альбома «Вася Тестировщик»?”
Слушайте на всех популярных площадках мой альбом в духе хип-хопа
“Лоскутное одеяло“: https://band.link/penchwork
А также альбом с утяжелённым звучанием гитар
“Хлад металлов“: https://band.link/hlad_metallov
Также я публикуюсь и в других местах:
ВК: https://vk.com/dmisanin
Max: https://max.ru/join/eplS_t3xAWUrdsb52ojWxP3_gc1mbE3pE6c-Jv2-eE8
Telegram: https://t.me/shine_sean (Дзен прицепом)
Suno: https://suno.com/@dmisanin3
Sferoom: https://lk.sferoom.space/artists/117182/feed
Boosty: https://boosty.to/dmisanin (тут можно задонатить)
YouTube: https://www.youtube.com/@dmisanin (не забудьте про VPN)
Автор: Dmitry_Isanin


