Назирокодил утилиту на Kotlin для создания аккордов в любой тональности. Kotlin.. Kotlin. zerocode.. Kotlin. zerocode. zerocoding.. Kotlin. zerocode. zerocoding. ИИ.. Kotlin. zerocode. zerocoding. ИИ. искуственный интеллект.. Kotlin. zerocode. zerocoding. ИИ. искуственный интеллект. музыкальная теория.. Kotlin. zerocode. zerocoding. ИИ. искуственный интеллект. музыкальная теория. музыкальные сервисы.. Kotlin. zerocode. zerocoding. ИИ. искуственный интеллект. музыкальная теория. музыкальные сервисы. Тестирование IT-систем.. Kotlin. zerocode. zerocoding. ИИ. искуственный интеллект. музыкальная теория. музыкальные сервисы. Тестирование IT-систем. цепочка аккордов.

Хотел бы показать, как быстра может быть эволюция от идеи до готового кода с небездумным применением искусственного интеллекта (ИИ).

По работе у меня в road map (это, кто пока не в курсе, дорожная карта дел на год)

  • освоение ИИ в целях оптимизации тестирования;

  • написание автотестов в проекте на Kotlin.

Надо бы в этом всём прокачаться. Надо-надо… Надо подкачаться, вот и решил написать код на Kotlin с консультированием в текстовом чате с ИИ.

Давно я чаял идею написать утилиту для создания музыки, т.к. уж 20 лет как ею занимаюсь и ей обучаюсь любительски то тут, то там.

Любая музыка – суть последовательность аккордов. Если глубже, то последовательность функциональных ступеней в разных комбинациях, подчинённых законам муз.гармонии, описанных уже давно Хуго Риманом и уточнённых его последователями, но мы не будем в этот раз слишком глубоко нырять в гармонию).

Нам пока достаточно того, что упрощённо музыка – последовательность аккордов. Стало быть, чтобы создать музыку нужно эту последовательность создать = сгенерировать.
Спойлер:
Всё получилось. Утилиту сможете легко применить в вашем браузере. В статье описал как. Если чешутся руки погенерить аккорды мотайте сразу вниз на раздел “Как запустить утилиту?! В три простых действия получите аккорды и удовольствие :)

Первый блин.com

Сперва я составил промпт для текстового ИИ-ассистента (Qwen):

Помоги в простом формате написать программу на Kotlin, которая:

  1. Запрашивает и принимает у пользователя набор аккордов (string-слов) через пробел.

    1. Если пользователь не задаёт, по дефолту используем базовую белоклавишную тональность ля-минор/до-мажор (именно в ней те самые “блатные аккорды”):
      Am Bdim С Dm Em F G.

  2. Уточняет сколько аккордов должно быть в последовательности: 2, 3, 4 или больше (по дефолту – 3)

  3. Выдаёт как финальный результат рандомную последовательность из предложенных пользователем аккордов.

  4. Предлагает повторить сценарий.

ИИ сгенерил код:

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 базовых аккордов.

Собственно таблица тональностей, с которой пользователь может легко и удобно взаимодействовать через простой ввод числа и буков, и является самым большим достижением моего диалога с ИИ.

За 2 простых действия выберите любую тональность, известную современной музыке

За 2 простых действия выберите любую тональность, известную современной музыке

Те, кто прошёл музыкальную школу жизни отметят, что таблица упорядочена по родственным тональностям, имеющим одинаковое число знаков при ключе (♯/♭), что достаточно удобненько само по себе.

Как это получилось?

В чате у ИИ я попросил дать возможность пошагово сгенерировать последовательность аккордов:

  1. Составить таблицу тональностей, и словарь их аккордов.

  2. Дать пользователю возможность выбирать тональность и получить набор её базовых аккордов.

  3. Выбрать аккорды и задать число их случайных появлений в последовательности.

  4. Пользователь имеет возможность повторить ключевые шаги программы, то есть пересоздать последовательность аккордов по-другому.

Тут я включу 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), …

Назирокодил утилиту на Kotlin для создания аккордов в любой тональности - 3

Параметры для запуска можно задать в правом окне (2): каждой параметр (13, m и т.д.) на отдельной строке представляют собой ответы на вопросы из программы, где строки пронумерованы по порядку ввода:

Шаг 1: Выбор тональности:

  1. Введите номер строки:
    в примере 13

  2. Мажор (M) или минор (m):
    в примере m
    (если ничего не задать, оставив строку пустой будет Мажор)

    Шаг 2: Доступные аккорды выбранной тональности:

  3. Введите номера аккордов через пробел (Enter для всех):
    в примере 1 2 3 4 5 6 7 (но лучше для минора 2-ую ступень не выбирать ;) )
    (если ничего не задать, оставив строку пустой будут все 7 аккордов)

    Шаг 3: количество аккордов в последовательности

  4. Сколько аккордов в последовательности? (по умолчанию 4):
    в примере ничего не задано, просто по Enter оставил строку пустой.

  5. Дальше блок повторных вводов: там пока имеет смысл вводить только повторные генерации в той же тональности через ввод “1”. Я 3 раза ввёл “1” в своём примере и получил 3 последовательности.

    Входные параметры (Input) для взаимодействия с программой.

    Входные параметры (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.

Пути развития утилиты

  1. Надо в github такое оформлять, знаю. Пока ещё не сделал, ибо новичок в создании кода с нуля. Ещё бы и красиво портянку кода на классы разделить. Когда сделаю, обновлю эту статью.

  2. Можно добавить и табулатуры аккордов, и иные представления аккордов, но это, пожалуй, в отдельном этапе.

  3. Хотел подарить вам эту утилиту в виде Telegram-бота. Научился разворачивать, подтягиваю зависимости либы com.github.kotlintelegrambot% через gradle и даже запустил бота с токеном от @BotFather из Телеграм, но, увы, сейчас Телега движется туго, надо как-то через прокси или не с российским IP разворачивать удалённо на сервере, поэтому реализация через ТГ-бот отложена.
    Бот в Макс только через ЮЛ, у меня только самозанятость оформлена пока.

Как использовать

  1. Индивидуальная практика в музыке. На гитаре, укулеле или клавишах сыграйте все аккорды – получите опыт и навык и хвастайтесь друзьям ачивкой!

  2. Практика в коллективе. Сыграйте с вашим рок-бэндом, докрутите последовательность, как вам надо – и играйте новую песни с вашими аккордами, ведь мне они не принадлежат, музыка – общее достояние.

  3. Можете “докрутить” сам код утилиты и сделать вашу утилиту, которая, например,

    1. будет генерировать последовательности по строгим законам гармонии (T-S-D) или допускать, как в блюзе нарушения строгости (T-D-S),

    2. будет давать некие отклонения или модуляции из тональности в тональность.

    3. Будет транспонировать цепочку сгенерированных аккордов (=переводить в другую тональность)

    4. … Ещё масса вариантов. Делитесь вашими идеями, и успехов их в реализации!

Желаю интересных генераций, а также хорошей и – главное – качественной музыки!


Назирокодил утилиту на Kotlin для создания аккордов в любой тональности - 5

Дмитрий Исанин

Тестировщик ПО, сонграйтер и исполнитель, изучаю и применяю ИТ и ИИ для создания песен и музыки, а также озвучки

Про то, как делаю музыку в последние 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
Boostyhttps://boosty.to/dmisanin (тут можно задонатить)
YouTubehttps://www.youtube.com/@dmisanin (не забудьте про VPN)

Автор: Dmitry_Isanin

Источник