├── .circleci └── config.yml ├── .github └── workflows │ └── build.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── assets ├── All_Star.mid ├── Birds_Flying_High.mid ├── Piano_Track_1.mid ├── Piano_Track_2.mid ├── Silent_Night.mid └── Yet_Dre.mid ├── build.gradle.kts ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src └── main ├── kotlin └── intellij │ └── music │ ├── core │ ├── ApmController.kt │ ├── CircleOfFifthsSequencer.kt │ ├── KeyboardStorage.kt │ ├── MajorScaleSequencer.kt │ ├── MidiBackend.kt │ ├── MidiDirectoryWatcher.kt │ ├── MidiFileController.kt │ ├── MidiFilePlayer.kt │ ├── MidiNotes.kt │ ├── MidiNotesPlayer.kt │ ├── MinorScaleSequencer.kt │ ├── MusicAlgorithmType.kt │ ├── MusicController.kt │ ├── RandomNotesController.kt │ ├── ScaleSequencer.kt │ ├── UserDirectoryLoader.kt │ └── util.kt │ └── ui │ ├── MusicApplicationComponent.kt │ ├── MusicConfig.kt │ ├── MusicConfigurable.kt │ ├── MusicConfigurableGUI.form │ ├── MusicConfigurableGUI.kt │ ├── MusicKeyEventListener.kt │ ├── MusicKeyboardEvent.kt │ ├── MusicTypedHandlerDelegate.kt │ └── actions │ ├── MuteAction.kt │ ├── NextTrackAction.kt │ ├── SwitchToMajorScaleAction.kt │ ├── SwitchToMinorScaleAction.kt │ ├── SwitchToRandomScaleAction.kt │ └── SwitchToSequentialAction.kt └── resources └── META-INF ├── plugin.xml └── pluginIcon.svg /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | jobs: 3 | build: 4 | parallelism: 2 5 | environment: 6 | _JAVA_OPTIONS: "-Xmx3g" 7 | GRADLE_OPTS: "-Dorg.gradle.daemon=false -Dorg.gradle.workers.max=2" 8 | docker: 9 | - image: circleci/openjdk:11.0.3-jdk-stretch 10 | working_directory: ~/intellij-music 11 | steps: 12 | - checkout 13 | - restore_cache: 14 | key: v1-gradle-wrapper-{{ checksum "gradle/wrapper/gradle-wrapper.properties" }} 15 | - restore_cache: 16 | key: v1-gradle-cache-{{ checksum "build.gradle.kts" }} 17 | - run: gradle build 18 | - save_cache: 19 | paths: 20 | - ~/.gradle/wrapper 21 | key: v1-gradle-wrapper-{{ checksum "gradle/wrapper/gradle-wrapper.properties" }} 22 | - save_cache: 23 | paths: 24 | - ~/.gradle/caches 25 | key: v1-gradle-cache-{{ checksum "build.gradle.kts" }} 26 | 27 | workflows: 28 | version: 2 29 | workflow: 30 | jobs: 31 | - build 32 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | container: 9 | image: gradle:6.2.2-jdk11 10 | steps: 11 | - uses: actions/checkout@v2 12 | - name: Get plugin version 13 | run: echo ::set-env name=PLUGIN_VERSION::$(gradle properties -q | grep "version:" | awk '{print $2}') 14 | - name: Build plugin 15 | run: gradle buildPlugin 16 | - name: Upload plugin 17 | uses: actions/upload-artifact@v1 18 | with: 19 | name: "intellij-music-${{ env.PLUGIN_VERSION }}-${{ github.sha }}.zip" 20 | path: "build/distributions/intellij-music-${{ env.PLUGIN_VERSION }}.zip" 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | .gradle 3 | .idea 4 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at ivmposti@gmail.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Fancy Music Team 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # intellij-music (Fancy Music Plugin) 2 | [![DevHack 2019](https://img.shields.io/badge/DevHack-2019-green)](https://wiki.compscicenter.ru/index.php/Devdays_Осень_2019_(проекты)) 3 | [![JetBrains plugins](https://img.shields.io/jetbrains/plugin/v/13231-fancy-music.svg)](https://plugins.jetbrains.com/plugin/13231-fancy-music) 4 | [![GitHub stars](https://img.shields.io/github/stars/FirstTimeInForever/intellij-music)](https://github.com/FirstTimeInForever/intellij-music/stargazers) 5 | [![GitHub issues](https://img.shields.io/github/issues/FirstTimeInForever/intellij-music)](https://github.com/FirstTimeInForever/intellij-music/issues) 6 | [![License](https://img.shields.io/github/license/FirstTimeInForever/intellij-music)](https://github.com/FirstTimeInForever/intellij-music/blob/master/LICENSE) 7 | [![GitHub forks](https://img.shields.io/github/forks/FirstTimeInForever/intellij-music)](https://github.com/FirstTimeInForever/intellij-music/network/members) 8 | 9 | [![CircleCI](https://circleci.com/gh/FirstTimeInForever/intellij-music.svg?style=svg)](https://circleci.com/gh/FirstTimeInForever/intellij-music) 10 | [![Latest release](https://img.shields.io/github/v/tag/firsttimeinforever/intellij-music?include_prereleases)](https://github.com/FirstTimeInForever/intellij-music/releases) 11 | [![GitHub contributors](https://img.shields.io/github/contributors/firsttimeinforever/intellij-music)](https://github.com/FirstTimeInForever/intellij-music/graphs/contributors) 12 | [![Latest commit](https://img.shields.io/github/last-commit/firsttimeinforever/intellij-music)](https://github.com/FirstTimeInForever/intellij-music/commits/master) 13 | 14 | 15 | Plays fancy music based on your keyboard activity. 16 | 17 | ## Description 18 | This plugin plays random notes from random major/minor scales based on your typing speed. Everytime you press the key, it plays random piano note based on some fancy (not really) music theory. 19 | 20 | Also, this plugin can play your custom midi files! Just put them into your midi directory! 21 | 22 | 23 | ## Installation instructions 24 | * Open any JetBrains IDE, e.g. [IntelliJ IDEA Community](https://www.jetbrains.com/idea/download/) 25 | * Install [the plugin](https://plugins.jetbrains.com/plugin/13231-fancy-music) ([instruction](https://www.jetbrains.com/help/idea/managing-plugins.html)) 26 | * Restart IDE and wait until download of music files is finished 27 | * Open any file and start typing. 28 | * ... 29 | * Enjoy the music! 30 | 31 | You can also change music type in settings/action pallet (piano scales or custom midis), and even choose directory with you own midi files. 32 | 33 | 34 | ## Getting involved 35 | 36 | * If you want to contribute please feel free to submit pull requests. 37 | * If you have a feature request please open an issue. 38 | -------------------------------------------------------------------------------- /assets/All_Star.mid: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FirstTimeInForever/intellij-music/b3a046357463ffe107aa056c136de4c1a9b8030f/assets/All_Star.mid -------------------------------------------------------------------------------- /assets/Birds_Flying_High.mid: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FirstTimeInForever/intellij-music/b3a046357463ffe107aa056c136de4c1a9b8030f/assets/Birds_Flying_High.mid -------------------------------------------------------------------------------- /assets/Piano_Track_1.mid: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FirstTimeInForever/intellij-music/b3a046357463ffe107aa056c136de4c1a9b8030f/assets/Piano_Track_1.mid -------------------------------------------------------------------------------- /assets/Piano_Track_2.mid: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FirstTimeInForever/intellij-music/b3a046357463ffe107aa056c136de4c1a9b8030f/assets/Piano_Track_2.mid -------------------------------------------------------------------------------- /assets/Silent_Night.mid: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FirstTimeInForever/intellij-music/b3a046357463ffe107aa056c136de4c1a9b8030f/assets/Silent_Night.mid -------------------------------------------------------------------------------- /assets/Yet_Dre.mid: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FirstTimeInForever/intellij-music/b3a046357463ffe107aa056c136de4c1a9b8030f/assets/Yet_Dre.mid -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.jetbrains.intellij.tasks.PublishTask 2 | import org.jetbrains.intellij.tasks.RunIdeTask 3 | import org.jetbrains.kotlin.gradle.tasks.KotlinCompile 4 | 5 | plugins { 6 | id("org.jetbrains.intellij") version "0.4.21" 7 | java 8 | kotlin("jvm") version "1.3.72" 9 | } 10 | 11 | group = "intellij.music" 12 | version = "1.0.2" 13 | 14 | repositories { 15 | jcenter() 16 | mavenCentral() 17 | } 18 | 19 | dependencies { 20 | implementation(kotlin("stdlib-jdk8")) 21 | testCompile("junit", "junit", "4.12") 22 | } 23 | 24 | // See https://github.com/JetBrains/gradle-intellij-plugin/ 25 | intellij { 26 | version = "2020.1" 27 | } 28 | configure { 29 | sourceCompatibility = JavaVersion.VERSION_1_8 30 | } 31 | tasks.getByName("patchPluginXml") { 32 | sinceBuild("182") 33 | untilBuild("700") 34 | changeNotes("""Initial version""") 35 | } 36 | tasks.withType { 37 | kotlinOptions.jvmTarget = "1.8" 38 | } 39 | tasks.withType { 40 | jvmArgs("-Xmx1536m") 41 | } 42 | 43 | tasks.withType { 44 | token(System.getenv("ORG_GRADLE_PROJECT_intellijPublishToken")) 45 | } 46 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | kotlin.code.style=official -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FirstTimeInForever/intellij-music/b3a046357463ffe107aa056c136de4c1a9b8030f/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.2.2-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS='"-Xmx64m"' 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS="-Xmx64m" 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'intellij-music' 2 | 3 | -------------------------------------------------------------------------------- /src/main/kotlin/intellij/music/core/ApmController.kt: -------------------------------------------------------------------------------- 1 | package intellij.music.core 2 | 3 | import java.util.* 4 | 5 | class ApmController(val onApmUpdate: (actionsPerSecond: Float, timeSinceLastAction: Long) -> Unit) { 6 | init { 7 | Thread { 8 | Thread.sleep(5000) 9 | while (true) { 10 | recalcApm() 11 | Thread.sleep(RECALC_INTERVAL) 12 | } 13 | }.start() 14 | } 15 | 16 | private val actionTimes: Deque = ArrayDeque() 17 | 18 | fun onAction() { 19 | val currentTime = System.currentTimeMillis() 20 | synchronized(this) { 21 | actionTimes.addLast(currentTime) 22 | } 23 | } 24 | 25 | private fun recalcApm() { 26 | val currentTime = System.currentTimeMillis() 27 | val (numberActions, millisecondsSinceLastAction) = synchronized(this) { 28 | while (actionTimes.peekFirst()?.let { currentTime - it > WINDOW_LENGTH } == true) { 29 | actionTimes.removeFirst() 30 | } 31 | val millisecondsSinceLastAction = actionTimes.peekLast()?.let { currentTime - it } ?: 1000 32 | Pair(actionTimes.size, millisecondsSinceLastAction) 33 | } 34 | 35 | val actionsPerWindow = numberActions.toFloat() 36 | val actionsPerSecond = actionsPerWindow / (WINDOW_LENGTH / 1000) 37 | onApmUpdate(actionsPerSecond, millisecondsSinceLastAction) 38 | } 39 | 40 | companion object { 41 | const val RECALC_INTERVAL: Long = 300 // milliseconds 42 | const val WINDOW_LENGTH: Long = 5 * 1000 // milliseconds 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/kotlin/intellij/music/core/CircleOfFifthsSequencer.kt: -------------------------------------------------------------------------------- 1 | package intellij.music.core 2 | 3 | 4 | 5 | class CircleOfFifthsSequencer { 6 | private var previousNote = Pair("C", 3) 7 | private val modes: List = listOf(MajorScaleSequencer(), MinorScaleSequencer()) 8 | private var currentModeIndex = 0 9 | 10 | fun nextNote(): Int { 11 | previousNote = modes[currentModeIndex].nextNote(previousNote) 12 | return MidiNotes.noteToMidi(previousNote) 13 | } 14 | 15 | fun getChord(): List { 16 | val chord = modes[currentModeIndex].getChord() 17 | val result = mutableListOf() 18 | for(note in chord) { 19 | result.add(MidiNotes.noteToMidi(note)) 20 | } 21 | return result 22 | } 23 | 24 | fun setCurrentScale(major: Boolean) { 25 | if(major) { 26 | currentModeIndex = 0 27 | } 28 | else { 29 | currentModeIndex = 1 30 | } 31 | } 32 | 33 | fun changeMode() { 34 | println("Trying to change mode") 35 | currentModeIndex = modes.size - 1 - selectRandom(listOf(0, 1), listOf(0.5, 0.5)) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/kotlin/intellij/music/core/KeyboardStorage.kt: -------------------------------------------------------------------------------- 1 | package intellij.music.core 2 | 3 | import intellij.music.ui.MusicKeyboardEvent 4 | import java.util.* 5 | import java.util.concurrent.ConcurrentLinkedDeque 6 | import java.util.concurrent.TimeUnit 7 | 8 | 9 | class KeyboardStorage(timeInterval: Double) { 10 | data class MusicEvent( 11 | val time: Date, 12 | val event: MusicKeyboardEvent 13 | ) 14 | 15 | private var eventsQueue: Queue = ConcurrentLinkedDeque() 16 | 17 | var interval: Double = timeInterval 18 | val frequency: Double 19 | get() = eventsQueue.size / interval 20 | 21 | 22 | fun addEvent(event: MusicKeyboardEvent) { 23 | val now = Date() 24 | 25 | eventsQueue.removeIf { element -> 26 | TimeUnit.SECONDS.toSeconds(now.time - element.time.time) <= this.interval 27 | } 28 | 29 | eventsQueue.add(MusicEvent(now, event)) 30 | // print(frequency) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/kotlin/intellij/music/core/MajorScaleSequencer.kt: -------------------------------------------------------------------------------- 1 | package intellij.music.core 2 | 3 | import kotlin.random.Random 4 | 5 | class MajorScaleSequencer: ScaleSequencer { 6 | private var currentScale = "C" 7 | private var notesPlayedInScale = 0 8 | private var notesPlayedInOctave = 0 9 | private var currentOctave = listOf(MidiNotes.BASE_OCTAVE, MidiNotes.BASE_OCTAVE + 1).random() 10 | private var previousNoteIndex: Int = 0 11 | 12 | override fun nextNote(previousNote: Pair): Pair { 13 | notesPlayedInOctave += 1 14 | notesPlayedInScale += 1 15 | tryToChangeScale() 16 | return shift() 17 | } 18 | 19 | override fun getChord(): List> { 20 | val base = Pair(MidiNotes.majorScaleNotes[currentScale]!![0], currentOctave) 21 | val third = Pair(MidiNotes.majorScaleNotes[currentScale]!![2], currentOctave) 22 | val fifth = Pair(MidiNotes.majorScaleNotes[currentScale]!![5], currentOctave) 23 | return listOf(base, third, fifth) 24 | } 25 | 26 | private fun shift(): Pair { 27 | previousNoteIndex = if (modeShifts[previousNoteIndex].isNotEmpty()) { 28 | modeShifts[previousNoteIndex].random() 29 | } 30 | else { 31 | listOf(0, 4, 5, 7).random() 32 | } 33 | val scale = MidiNotes.majorScaleNotes[currentScale] ?: error("Failed to get scale!") 34 | if (Random.nextBoolean()) { 35 | if (previousNoteIndex == 0 || previousNoteIndex == 7) { 36 | val octaveShift = MidiNotes.octaveShift(notesPlayedInOctave, currentOctave) 37 | if(octaveShift != 0) { 38 | notesPlayedInOctave = 0 39 | currentOctave += octaveShift 40 | } 41 | } 42 | } 43 | return Pair(scale[previousNoteIndex], currentOctave) 44 | } 45 | 46 | private fun tryToChangeScale() { 47 | val pr = notesPlayedInScale * 0.001 48 | if (Random.nextFloat() < pr) { 49 | currentScale = MidiNotes.majorScaleNotes.keys.random() 50 | println("Changed scale to: $currentScale") 51 | notesPlayedInScale = 0 52 | } 53 | } 54 | 55 | 56 | companion object { 57 | val modeShifts: List> = listOf( 58 | listOf(1, 2, 3, 4, 5, 6, 7), 59 | listOf(), 60 | listOf(), 61 | listOf(1), 62 | listOf(4, 7), 63 | listOf(), 64 | listOf(5, 1), 65 | listOf(1, 2, 3, 4, 5, 6, 7) 66 | ) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/kotlin/intellij/music/core/MidiBackend.kt: -------------------------------------------------------------------------------- 1 | package intellij.music.core 2 | 3 | import java.io.File 4 | import javax.sound.midi.MidiSystem 5 | import javax.sound.midi.Synthesizer 6 | 7 | class MidiBackend(private val soundfontFile: File) { 8 | val synthesizer: Synthesizer = MidiSystem.getSynthesizer() 9 | 10 | init { 11 | init() 12 | } 13 | 14 | private fun init() { 15 | synthesizer.open() 16 | synthesizer.unloadAllInstruments(synthesizer.defaultSoundbank) 17 | val soundbank = MidiSystem.getSoundbank(soundfontFile) 18 | synthesizer.loadAllInstruments(soundbank) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/kotlin/intellij/music/core/MidiDirectoryWatcher.kt: -------------------------------------------------------------------------------- 1 | package intellij.music.core 2 | 3 | import java.io.File 4 | import java.nio.file.FileSystems 5 | import java.nio.file.StandardWatchEventKinds.* 6 | import java.nio.file.WatchKey 7 | 8 | class MidiDirectoryWatcher(private val onDirectoryContentChange: () -> Unit) { 9 | private val watcher = FileSystems.getDefault().newWatchService() 10 | @Volatile 11 | private var currentKey: WatchKey? = null 12 | 13 | init { 14 | Thread { 15 | while (true) { 16 | val key = watcher.take() 17 | key.pollEvents() 18 | onDirectoryContentChange() 19 | key.reset() 20 | } 21 | }.start() 22 | } 23 | 24 | fun setDirectory(directory: File) { 25 | currentKey?.cancel() 26 | currentKey = directory.toPath().register(watcher, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/kotlin/intellij/music/core/MidiFileController.kt: -------------------------------------------------------------------------------- 1 | package intellij.music.core 2 | 3 | import intellij.music.ui.MusicKeyboardEvent 4 | import java.io.File 5 | import kotlin.random.Random 6 | 7 | class MidiFileController(midiBackend: MidiBackend, 8 | private val keyboardStorage: KeyboardStorage, 9 | private val userFiles: MutableList) { 10 | private val midiFilePlayer: MidiFilePlayer = MidiFilePlayer(midiBackend) 11 | private val apmController: ApmController = ApmController(::onApmUpdate) 12 | private var isActive: Boolean = false 13 | private var currentFileIndex: Int = -1 14 | 15 | init { 16 | nextTrack() 17 | midiFilePlayer.addPlayEndEventListener { 18 | nextTrack() 19 | } 20 | } 21 | 22 | fun nextTrack() { 23 | currentFileIndex += 1 24 | if(currentFileIndex >= userFiles.size) { 25 | currentFileIndex = 0 26 | } 27 | setCurrentFile() 28 | } 29 | 30 | fun setRandomTrack() { 31 | currentFileIndex = Random.nextInt(userFiles.size) 32 | setCurrentFile() 33 | } 34 | 35 | fun setCurrentFile() { 36 | val file = userFiles[currentFileIndex] 37 | println("Start midi file: $file") 38 | 39 | midiFilePlayer.setAudioFile(file) 40 | midiFilePlayer.playFile() 41 | midiFilePlayer.pause() 42 | } 43 | 44 | fun reloadSequencer() { 45 | midiFilePlayer.reload() 46 | } 47 | 48 | fun keyboardPressed(event: MusicKeyboardEvent) { 49 | checkActive() 50 | keyboardStorage.addEvent(event) 51 | apmController.onAction() 52 | } 53 | 54 | private fun checkActive() { 55 | if (!isActive) { 56 | isActive = true 57 | midiFilePlayer.resume() 58 | } 59 | } 60 | 61 | private fun onApmUpdate(actionsPerSecond: Float, millisecondsSinceLastAction: Long) { 62 | if (millisecondsSinceLastAction > 300) { 63 | midiFilePlayer.pause() 64 | isActive = false 65 | return 66 | } 67 | 68 | val bpmMultiplier = calculateBpm(actionsPerSecond).toFloat() 69 | midiFilePlayer.setBpmMultiplier(bpmMultiplier) 70 | System.err.println("aps: $actionsPerSecond bpm: $bpmMultiplier") 71 | } 72 | 73 | private fun calculateBpm(actionsPerSecond: Float): Double { 74 | return when { 75 | actionsPerSecond < 0.5 -> 0.5 + 0.1 * actionsPerSecond / 0.5 76 | actionsPerSecond < 1.0 -> 0.6 + 0.1 * (actionsPerSecond - 0.5) / (1 - 0.5) 77 | actionsPerSecond < 3.0 -> 0.7 + 0.1 * (actionsPerSecond - 1) / (3 - 1) 78 | actionsPerSecond < 5.0 -> 0.8 + 0.2 * (actionsPerSecond - 3) / (5 - 3) 79 | actionsPerSecond < 10 -> 1.0 + 0.2 * (actionsPerSecond - 5) / (10 - 5) 80 | else -> 1.2 + 0.2 * (actionsPerSecond - 10) / (30 - 10) 81 | // else -> (0.7 + log10(1 + actionsPerSecond / 2)) 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/main/kotlin/intellij/music/core/MidiFilePlayer.kt: -------------------------------------------------------------------------------- 1 | package intellij.music.core 2 | 3 | import java.io.BufferedInputStream 4 | import java.io.File 5 | import java.io.FileInputStream 6 | import java.io.InputStream 7 | import javax.sound.midi.MetaEventListener 8 | import javax.sound.midi.MidiSystem 9 | 10 | class MidiFilePlayer(val backend: MidiBackend) { 11 | private val sequencer = MidiSystem.getSequencer(false) 12 | private var inputStream: InputStream? = null 13 | private var baseBpm: Float = 1f 14 | 15 | init { 16 | init() 17 | } 18 | 19 | fun setAudioFile(file: File) { 20 | inputStream?.close() 21 | inputStream = BufferedInputStream(FileInputStream(file)) 22 | } 23 | 24 | fun setBpmMultiplier(multiplier: Float) { 25 | sequencer.tempoInBPM = baseBpm * multiplier 26 | } 27 | 28 | fun addPlayEndEventListener(callback: () -> Unit) { 29 | sequencer.addMetaEventListener(MetaEventListener { 30 | if(it.type == 0x2f) { 31 | callback() 32 | } 33 | }) 34 | } 35 | 36 | fun init() { 37 | sequencer.transmitter.receiver = backend.synthesizer.receiver 38 | sequencer.open() 39 | } 40 | 41 | fun unload() { 42 | inputStream?.close() 43 | sequencer.close() 44 | } 45 | 46 | fun reload() { 47 | unload() 48 | init() 49 | } 50 | 51 | fun pause() { 52 | if (sequencer.isOpen) { 53 | sequencer.stop() 54 | } 55 | } 56 | 57 | fun resume() { 58 | sequencer.start() 59 | } 60 | 61 | fun playFile(initialBpmMultiplier: Float = 1f) { 62 | sequencer.setSequence(inputStream) 63 | baseBpm = sequencer.tempoInBPM 64 | setBpmMultiplier(initialBpmMultiplier) 65 | sequencer.start() 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/kotlin/intellij/music/core/MidiNotes.kt: -------------------------------------------------------------------------------- 1 | package intellij.music.core 2 | 3 | import kotlin.math.abs 4 | import kotlin.random.Random 5 | 6 | object MidiNotes { 7 | fun noteToMidi(note: Pair): Int { 8 | val index = notes.indexOf(note.first) 9 | return index + note.second * notes.size 10 | } 11 | 12 | fun octaveShift(notesPlayed: Int, currentOctave: Int): Int { 13 | fun sign(x: Double): Int { 14 | return if (x < 0) { 15 | -1 16 | } else { 17 | +1 18 | } 19 | } 20 | var pr = notesPlayed * 0.1 21 | var shift = 0 22 | if (Random.nextFloat() < pr) { 23 | pr = abs(3.0 - currentOctave) / 7.0 24 | pr *= 2.0 25 | shift = selectRandom(listOf(-1, +1), listOf(pr, 1.0 - pr)) * sign(pr) 26 | } 27 | if(shift + currentOctave < 1) { 28 | shift = 0 29 | } 30 | else if(shift + currentOctave > 7) { 31 | shift = 0 32 | } 33 | return shift 34 | } 35 | 36 | val BASE_OCTAVE: Int = 3 37 | 38 | val notes = listOf("C", "Db", "D", "Eb", "E", "F", "Gb", "G", "Ab", "A", "Bb", "B") 39 | get() = field 40 | 41 | val majorScaleNotes: Map> = mapOf( 42 | "C" to listOf("C", "D", "E", "F", "G", "A", "B", "C"), 43 | "D" to listOf("D", "E", "Gb", "G", "A", "B", "Db", "D"), 44 | "E" to listOf("E", "Gb", "Ab", "A", "B", "Db", "Eb", "E"), 45 | "F" to listOf("F", "G", "A", "Bb", "C", "D", "E", "F"), 46 | "G" to listOf("G", "A", "B", "C", "D", "E", "Gb", "G"), 47 | "A" to listOf("A", "B", "Db", "D", "E", "Gb", "Ab", "A"), 48 | "B" to listOf("B", "Db", "Eb", "E", "Gb", "Ab", "Bb", "B") 49 | ) 50 | get() = field 51 | 52 | val minorScaleNotes: Map> = mapOf( 53 | "A" to listOf("Bb", "Cb", "Db", "Eb", "Fb", "Gb", "Ab", "Bb"), 54 | "B" to listOf("B", "Db", "D", "E", "Gb", "G", "A", "B"), 55 | "C" to listOf("C", "D", "Eb", "F", "G", "Ab", "Bb", "C"), 56 | "D" to listOf("D", "E", "F", "G", "A", "Bb", "C", "D"), 57 | "E" to listOf("E", "Gb", "G", "A", "B", "C", "D", "E"), 58 | "F" to listOf("F", "G", "Ab", "Bb", "C", "Db", "Eb", "F"), 59 | "G" to listOf("G", "A", "Bb", "C", "D", "Eb", "F", "G") 60 | ) 61 | } 62 | -------------------------------------------------------------------------------- /src/main/kotlin/intellij/music/core/MidiNotesPlayer.kt: -------------------------------------------------------------------------------- 1 | package intellij.music.core 2 | 3 | import java.util.* 4 | import javax.sound.midi.MidiChannel 5 | import kotlin.concurrent.timerTask 6 | 7 | class MidiNotesPlayer(backend: MidiBackend) { 8 | private val channel: MidiChannel = backend.synthesizer.channels[0] 9 | private var prevNote: Int = 0 10 | private val timer = Timer() 11 | 12 | fun ensureResetNotes() { 13 | // backend.reload() 14 | channel.allNotesOff() 15 | } 16 | 17 | fun playNote(note: Int, velocity: Int) { 18 | if (channel.program != 0) { 19 | channel.programChange(0, 0) 20 | } 21 | 22 | channel.noteOn(note, velocity) 23 | prevNote = note 24 | timer.schedule(timerTask { 25 | channel.noteOff(note) 26 | }, velocity.toLong()) 27 | } 28 | 29 | fun playChord(notes: List, velocity: Int) { 30 | for(note in notes) { 31 | channel.noteOn(note, velocity) 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/kotlin/intellij/music/core/MinorScaleSequencer.kt: -------------------------------------------------------------------------------- 1 | package intellij.music.core 2 | 3 | import kotlin.random.Random 4 | 5 | class MinorScaleSequencer: ScaleSequencer { 6 | private var currentScale = "A" 7 | private var notesPlayedInScale = 0 8 | private var notesPlayedInOctave = 0 9 | private var currentOctave = listOf(MidiNotes.BASE_OCTAVE, MidiNotes.BASE_OCTAVE + 1).random() 10 | private var previousNoteIndex: Int = 0 11 | 12 | override fun nextNote(previousNote: Pair): Pair { 13 | notesPlayedInOctave += 1 14 | notesPlayedInScale += 1 15 | tryToChangeScale() 16 | return shift() 17 | } 18 | 19 | override fun getChord(): List> { 20 | val base = Pair(MidiNotes.minorScaleNotes[currentScale]!![0], currentOctave) 21 | val third = Pair(MidiNotes.minorScaleNotes[currentScale]!![2], currentOctave) 22 | val fifth = Pair(MidiNotes.minorScaleNotes[currentScale]!![5], currentOctave) 23 | return listOf(base, third, fifth) 24 | } 25 | 26 | private fun shift(): Pair { 27 | previousNoteIndex = if (modeShifts[previousNoteIndex].isNotEmpty()) { 28 | modeShifts[previousNoteIndex].random() 29 | } 30 | else { 31 | listOf(0, 4, 5, 7).random() 32 | } 33 | val scale = MidiNotes.minorScaleNotes[currentScale] ?: error("Failed to get scale!") 34 | if (Random.nextBoolean()) { 35 | if (previousNoteIndex == 0 || previousNoteIndex == 7) { 36 | val octaveShift = MidiNotes.octaveShift(notesPlayedInOctave, currentOctave) 37 | if(octaveShift != 0) { 38 | notesPlayedInOctave = 0 39 | currentOctave += octaveShift 40 | } 41 | } 42 | } 43 | return Pair(scale[previousNoteIndex], currentOctave) 44 | } 45 | 46 | private fun tryToChangeScale() { 47 | val pr = notesPlayedInScale * 0.001 48 | if (Random.nextFloat() < pr) { 49 | currentScale = MidiNotes.minorScaleNotes.keys.random() 50 | println("Changed scale to: $currentScale") 51 | notesPlayedInScale = 0 52 | } 53 | } 54 | 55 | 56 | companion object { 57 | val modeShifts: List> = listOf( 58 | listOf(1, 2, 3, 4, 5, 6, 7), 59 | listOf(3, 4), 60 | listOf(4), 61 | listOf(2, 7), 62 | listOf(7), 63 | listOf(2, 4, 1), 64 | listOf(1, 3), 65 | listOf(1, 2, 3, 4, 5, 6, 7) 66 | ) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/kotlin/intellij/music/core/MusicAlgorithmType.kt: -------------------------------------------------------------------------------- 1 | package intellij.music.core 2 | 3 | enum class MusicAlgorithmType { 4 | RANDOM_MAJOR, 5 | RANDOM_MINOR, 6 | RANDOM_BOTH, 7 | SEQUENTIAL, 8 | } 9 | -------------------------------------------------------------------------------- /src/main/kotlin/intellij/music/core/MusicController.kt: -------------------------------------------------------------------------------- 1 | package intellij.music.core 2 | 3 | import intellij.music.ui.MusicConfig 4 | import intellij.music.ui.MusicKeyboardEvent 5 | import java.io.File 6 | 7 | /* 8 | Base controller class. 9 | */ 10 | class MusicController { 11 | private val config = MusicConfig.instance 12 | private val keyboardStorage: KeyboardStorage = KeyboardStorage(10.0) 13 | private val userFiles = UserDirectoryLoader() 14 | private var isSoundFontLoaded: Boolean = false 15 | 16 | private lateinit var midiBackend: MidiBackend 17 | private lateinit var midiFileController: MidiFileController 18 | private lateinit var randomNotesController: RandomNotesController 19 | 20 | init { 21 | userFiles.initSoundFont(::onSoundFontLoaded) 22 | } 23 | 24 | private fun onSoundFontLoaded(soundfontFile: File) { 25 | midiBackend = MidiBackend(soundfontFile) 26 | midiFileController = MidiFileController(midiBackend, keyboardStorage, userFiles.userFiles) 27 | randomNotesController = RandomNotesController(midiBackend) 28 | isSoundFontLoaded = true 29 | } 30 | 31 | fun keyboardPressed(event: MusicKeyboardEvent) { 32 | if (!isSoundFontLoaded) return 33 | 34 | when (config.algorithmType) { 35 | MusicAlgorithmType.SEQUENTIAL -> midiFileController.keyboardPressed(event) 36 | else -> randomNotesController.keyboardPressed(event) 37 | } 38 | } 39 | 40 | fun nextTrack() { 41 | midiFileController.nextTrack() 42 | } 43 | 44 | fun onSettingsChanged() { 45 | userFiles.reloadMidiFilesDirectory() 46 | reopenCurrentMidiFile() 47 | } 48 | 49 | private fun reopenCurrentMidiFile() { 50 | if (!isSoundFontLoaded) return 51 | if (config.algorithmType == MusicAlgorithmType.SEQUENTIAL) { 52 | midiFileController.setCurrentFile() 53 | } else { 54 | // todo ??? 55 | // randomNotesController.notesPlayer.ensureResetNotes() 56 | } 57 | } 58 | 59 | fun onSwitchMusicTypeAction() { 60 | reopenCurrentMidiFile() 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/kotlin/intellij/music/core/RandomNotesController.kt: -------------------------------------------------------------------------------- 1 | package intellij.music.core 2 | 3 | import intellij.music.ui.MusicConfig 4 | import intellij.music.ui.MusicKeyboardEvent 5 | import java.util.* 6 | import java.util.concurrent.TimeUnit 7 | 8 | class RandomNotesController(midiBackend: MidiBackend) { 9 | val notesPlayer = MidiNotesPlayer(midiBackend) 10 | private val circleSequencer = CircleOfFifthsSequencer() 11 | private var lastNoteTime = Date() 12 | private val config = MusicConfig.instance 13 | 14 | fun keyboardPressed(event: MusicKeyboardEvent) { 15 | circleSequencer.setCurrentScale(config.algorithmType == MusicAlgorithmType.RANDOM_MAJOR) 16 | val diff = TimeUnit.MILLISECONDS.toMillis(Date().time - lastNoteTime.time) 17 | if(diff < 130) { 18 | return 19 | } 20 | else if(MusicConfig.instance.algorithmType == MusicAlgorithmType.RANDOM_BOTH && diff > 900) { 21 | circleSequencer.changeMode() 22 | } 23 | if (diff > 900) { 24 | notesPlayer.ensureResetNotes() 25 | } 26 | if(event.numberModifiers != 0) { 27 | notesPlayer.playChord(circleSequencer.getChord(), BASE_NOTE_VELOCITY * 2) 28 | circleSequencer.nextNote() 29 | return 30 | } 31 | notesPlayer.playNote(circleSequencer.nextNote(), BASE_NOTE_VELOCITY) 32 | lastNoteTime = Date() 33 | } 34 | 35 | companion object { 36 | const val BASE_NOTE_VELOCITY: Int = 200 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/kotlin/intellij/music/core/ScaleSequencer.kt: -------------------------------------------------------------------------------- 1 | package intellij.music.core 2 | 3 | interface ScaleSequencer { 4 | fun nextNote(previousNote: Pair): Pair 5 | fun getChord(): List> 6 | } 7 | -------------------------------------------------------------------------------- /src/main/kotlin/intellij/music/core/UserDirectoryLoader.kt: -------------------------------------------------------------------------------- 1 | package intellij.music.core 2 | 3 | import com.intellij.openapi.progress.PerformInBackgroundOption 4 | import com.intellij.openapi.progress.ProgressIndicator 5 | import com.intellij.openapi.progress.ProgressManager 6 | import com.intellij.openapi.progress.Task 7 | import intellij.music.ui.MusicConfig 8 | import java.io.File 9 | import java.io.FileOutputStream 10 | import java.net.URL 11 | import java.nio.channels.Channels 12 | 13 | class UserDirectoryLoader { 14 | private val config = MusicConfig.instance 15 | private val pluginDirectory = defaultMidiDir 16 | private val soundFontFile = File(pluginDirectory, "soundfont.sf2") 17 | val userFiles = mutableListOf() 18 | 19 | private val midiDirectoryWatcher = MidiDirectoryWatcher(this::reindexFiles) 20 | 21 | private val userDirectory 22 | get() = File(config.midiDir) 23 | 24 | init { 25 | println("User directory path: $userDirectory") 26 | println("Plugin directory path: $pluginDirectory") 27 | if (!userDirectory.exists() && !userDirectory.mkdirs()) { 28 | throw RuntimeException("Could not create user directory ($userDirectory)") 29 | } 30 | if (!pluginDirectory.exists() && !pluginDirectory.mkdirs()) { 31 | throw RuntimeException("Could not create plugin directory ($pluginDirectory)") 32 | } 33 | } 34 | 35 | fun initSoundFont(onSoundFontLoaded: (File) -> Unit) { 36 | reindexFiles() 37 | if (soundFontFile.exists() && userFiles.isNotEmpty()) { 38 | onSoundFontLoaded(soundFontFile) 39 | } else { 40 | config.midiDir = defaultMidiDir.toString() 41 | downloadFontAndMidiFiles(onSoundFontLoaded) 42 | } 43 | midiDirectoryWatcher.setDirectory(userDirectory) 44 | } 45 | 46 | private fun downloadFile(url: String, file: File) { 47 | val website = URL(url) 48 | val rbc = Channels.newChannel(website.openStream()) 49 | val fos = FileOutputStream(file) 50 | fos.channel.transferFrom(rbc, 0, java.lang.Long.MAX_VALUE) 51 | } 52 | 53 | private fun downloadFontAndMidiFiles(onSoundFontLoaded: (File) -> Unit) { 54 | ProgressManager.getInstance().run(object : Task.Backgroundable( 55 | null, 56 | "Fancy Music: Downloading sound font...", 57 | false, 58 | PerformInBackgroundOption.ALWAYS_BACKGROUND 59 | ) { 60 | override fun run(indicator: ProgressIndicator) { 61 | println("Fancy Music: Downloading midi files...") 62 | for (file in initialMidiFiles) { 63 | downloadFile(initialMidiFilesUrl + file, File(userDirectory, file)) 64 | } 65 | reindexFiles() 66 | 67 | println("Fancy Music: Downloading sound font...") 68 | downloadFile(soundFontUrl, soundFontFile) 69 | onSoundFontLoaded(soundFontFile) 70 | } 71 | }) 72 | } 73 | 74 | fun reindexFiles() { 75 | val files: Array = userDirectory.listFiles { file -> !file.isDirectory && file.extension == "mid" } ?: emptyArray() 76 | println("Reindex, found files: ${files.map { it.name }}") 77 | 78 | userFiles.clear() 79 | userFiles.addAll(files) 80 | userFiles.shuffle() 81 | } 82 | 83 | fun reloadMidiFilesDirectory() { 84 | midiDirectoryWatcher.setDirectory(userDirectory) 85 | reindexFiles() 86 | } 87 | 88 | companion object { 89 | val defaultMidiDir = File(System.getProperty("user.home"), ".intellij-fancy-music-plugin") 90 | 91 | const val soundFontUrl = "https://raw.githubusercontent.com/urish/cinto/master/media/FluidR3%20GM.sf2" 92 | const val initialMidiFilesUrl = "https://raw.githubusercontent.com/FirstTimeInForever/intellij-music/master/assets/" 93 | val initialMidiFiles = listOf( 94 | "All_Star.mid", 95 | "Birds_Flying_High.mid", 96 | "Piano_Track_1.mid", 97 | "Piano_Track_2.mid", 98 | "Silent_Night.mid", 99 | "Yet_Dre.mid" 100 | ) 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/main/kotlin/intellij/music/core/util.kt: -------------------------------------------------------------------------------- 1 | package intellij.music.core 2 | 3 | public fun selectRandom(items: List, probabilities: List): T { 4 | val p = Math.random() 5 | var cumulativeProbability = 0.0 6 | for ((item, itemProbability) in (items zip probabilities)) { 7 | cumulativeProbability += itemProbability 8 | if (p <= cumulativeProbability) { 9 | return item 10 | } 11 | } 12 | return items.last() 13 | } 14 | -------------------------------------------------------------------------------- /src/main/kotlin/intellij/music/ui/MusicApplicationComponent.kt: -------------------------------------------------------------------------------- 1 | package intellij.music.ui 2 | 3 | import com.intellij.openapi.Disposable 4 | import com.intellij.openapi.application.ApplicationManager 5 | import com.intellij.openapi.components.BaseComponent 6 | import com.intellij.openapi.diagnostic.Logger 7 | import com.intellij.openapi.editor.Editor 8 | import com.intellij.openapi.editor.EditorFactory 9 | import com.intellij.openapi.editor.ex.EditorEventMulticasterEx 10 | import com.intellij.openapi.editor.ex.FocusChangeListener 11 | import intellij.music.core.MusicController 12 | import org.apache.log4j.Level 13 | 14 | 15 | class MusicApplicationComponent : BaseComponent { 16 | val controller: MusicController = MusicController() 17 | 18 | override fun initComponent() { 19 | LOG.setLevel(Level.INFO) 20 | LOG.info("Initializing plugin data structures") 21 | 22 | initFocusListener() 23 | MusicKeyEventListener().initKeyListener() 24 | } 25 | 26 | private fun initFocusListener() { 27 | val editorEventMulticaster = EditorFactory.getInstance().eventMulticaster as EditorEventMulticasterEx 28 | editorEventMulticaster.addFocusChangeListener(object : FocusChangeListener { 29 | override fun focusGained(editor: Editor) {} 30 | 31 | override fun focusLost(editor: Editor) { 32 | // stopMusic() 33 | } 34 | }, Disposable {}) 35 | } 36 | 37 | fun keyboardPressed(event: MusicKeyboardEvent) { 38 | ApplicationManager.getApplication().executeOnPooledThread { 39 | controller.keyboardPressed(event) 40 | } 41 | } 42 | 43 | override fun disposeComponent() { 44 | LOG.info("Disposing plugin data structures") 45 | } 46 | 47 | override fun getComponentName(): String { 48 | return "musicApplicationComponent" 49 | } 50 | 51 | companion object { 52 | private val LOG = Logger.getInstance(MusicApplicationComponent::class.java) 53 | 54 | val instance: MusicApplicationComponent 55 | get() = ApplicationManager.getApplication().getComponent(MusicApplicationComponent::class.java) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/main/kotlin/intellij/music/ui/MusicConfig.kt: -------------------------------------------------------------------------------- 1 | package intellij.music.ui 2 | 3 | import com.intellij.openapi.components.PersistentStateComponent 4 | import com.intellij.openapi.components.ServiceManager 5 | import com.intellij.openapi.components.State 6 | import com.intellij.openapi.components.Storage 7 | import com.intellij.util.xmlb.XmlSerializerUtil 8 | import intellij.music.core.MusicAlgorithmType 9 | import intellij.music.core.UserDirectoryLoader 10 | 11 | @State(name = "MusicConfig", storages = [(Storage("MusicConfig.xml"))]) 12 | class MusicConfig : PersistentStateComponent { 13 | 14 | var enabled: Boolean = true 15 | var onlyInEditor: Boolean = false 16 | var algorithmType = MusicAlgorithmType.RANDOM_MINOR 17 | var midiDir: String = UserDirectoryLoader.defaultMidiDir.toString() 18 | 19 | override fun getState(): MusicConfig = this 20 | 21 | override fun loadState(state: MusicConfig) = XmlSerializerUtil.copyBean(state, this) 22 | 23 | companion object { 24 | val instance: MusicConfig 25 | get() = ServiceManager.getService(MusicConfig::class.java) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/kotlin/intellij/music/ui/MusicConfigurable.kt: -------------------------------------------------------------------------------- 1 | package intellij.music.ui 2 | 3 | import com.intellij.openapi.options.SearchableConfigurable 4 | import javax.swing.JComponent 5 | import kotlin.properties.Delegates 6 | 7 | class MusicConfigurable : SearchableConfigurable { 8 | private var gui by Delegates.notNull() 9 | private val config = MusicConfig.instance 10 | 11 | override fun isModified(): Boolean { 12 | return gui.isModified(config) 13 | } 14 | 15 | override fun getId(): String { 16 | return "preference.IntellijMusic" 17 | } 18 | 19 | override fun getDisplayName(): String { 20 | return "Fancy Music Plugin" 21 | } 22 | 23 | override fun apply() { 24 | gui.saveToConfig(config) 25 | MusicApplicationComponent.instance.controller.onSettingsChanged() 26 | } 27 | 28 | override fun createComponent(): JComponent? { 29 | gui = MusicConfigurableGUI() 30 | gui.loadFromConfig(config) 31 | return gui.rootPanel 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/kotlin/intellij/music/ui/MusicConfigurableGUI.form: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 |
97 | -------------------------------------------------------------------------------- /src/main/kotlin/intellij/music/ui/MusicConfigurableGUI.kt: -------------------------------------------------------------------------------- 1 | package intellij.music.ui 2 | 3 | import com.intellij.openapi.fileChooser.FileChooser 4 | import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory 5 | import com.intellij.openapi.ui.Messages 6 | import com.intellij.openapi.ui.TextFieldWithBrowseButton 7 | import com.intellij.openapi.vfs.VirtualFileManager 8 | import intellij.music.core.MusicAlgorithmType 9 | import org.jetbrains.annotations.NonNls 10 | import java.awt.Component 11 | import java.io.File 12 | import javax.swing.JCheckBox 13 | import javax.swing.JPanel 14 | import javax.swing.JRadioButton 15 | 16 | class MusicConfigurableGUI { 17 | lateinit var rootPanel: JPanel 18 | private lateinit var enabled: JCheckBox 19 | private lateinit var onlyInEditor: JCheckBox 20 | 21 | private lateinit var musicTypeRandomMinor: JRadioButton 22 | private lateinit var musicTypeRandomMajor: JRadioButton 23 | private lateinit var musicTypeRandomBoth: JRadioButton 24 | private lateinit var musicTypeMidi: JRadioButton 25 | private lateinit var midiDirChoose: TextFieldWithBrowseButton 26 | 27 | init { 28 | midiDirChoose.addActionListener { 29 | @NonNls val path = midiDirChoose.text.trim { it <= ' ' } 30 | selectDirectory(path, this::onSelectDirectory, rootPanel) 31 | } 32 | 33 | enabled.addActionListener { updateEnabled() } 34 | musicTypeMidi.addActionListener { updateEnabled() } 35 | } 36 | 37 | private fun onSelectDirectory(directory: String) { 38 | val file = File(directory) 39 | val midiFiles = file.listFiles { midiFile -> midiFile.extension == "mid" } 40 | if (midiFiles?.isEmpty() == true) { 41 | Messages.showMessageDialog(null, "Please select directory which contains midi files", "Fancy Music", Messages.getInformationIcon()) 42 | return 43 | } 44 | 45 | midiDirChoose.text = directory 46 | } 47 | 48 | private fun selectDirectory(path0: String, dirConsumer: (String) -> Unit, component: Component?) { 49 | var path = path0 50 | val descriptor = FileChooserDescriptorFactory.createSingleFolderDescriptor() 51 | .withTitle("Select directory with midi files") 52 | .withShowFileSystemRoots(true) 53 | 54 | path = "file://" + path.replace(File.separatorChar, '/') 55 | val root = VirtualFileManager.getInstance().findFileByUrl(path) 56 | 57 | val file = FileChooser.chooseFile(descriptor, component, null, root) ?: return 58 | val resultPath = file.path.replace('/', File.separatorChar) 59 | dirConsumer(resultPath) 60 | } 61 | 62 | fun isModified(config: MusicConfig): Boolean { 63 | return enabled.isSelected != config.enabled 64 | || onlyInEditor.isSelected != config.onlyInEditor 65 | || currentAlgorithmType() != config.algorithmType 66 | || midiDirChoose.text != config.midiDir 67 | } 68 | 69 | fun loadFromConfig(config: MusicConfig) { 70 | midiDirChoose.text = config.midiDir 71 | 72 | enabled.isSelected = config.enabled 73 | onlyInEditor.isSelected = config.onlyInEditor 74 | 75 | musicTypeRandomMinor.isSelected = config.algorithmType == MusicAlgorithmType.RANDOM_MINOR 76 | musicTypeRandomMajor.isSelected = config.algorithmType == MusicAlgorithmType.RANDOM_MAJOR 77 | musicTypeRandomBoth.isSelected = config.algorithmType == MusicAlgorithmType.RANDOM_BOTH 78 | musicTypeMidi.isSelected = config.algorithmType == MusicAlgorithmType.SEQUENTIAL 79 | 80 | updateEnabled() 81 | } 82 | 83 | private fun updateEnabled() { 84 | val isEnabled = enabled.isSelected 85 | onlyInEditor.isEnabled = isEnabled 86 | musicTypeRandomMinor.isEnabled = isEnabled 87 | musicTypeRandomMajor.isEnabled = isEnabled 88 | musicTypeRandomBoth.isEnabled = isEnabled 89 | musicTypeMidi.isEnabled = isEnabled 90 | midiDirChoose.isEnabled = isEnabled && musicTypeMidi.isSelected 91 | } 92 | 93 | fun saveToConfig(config: MusicConfig) { 94 | config.enabled = enabled.isSelected 95 | config.onlyInEditor = onlyInEditor.isSelected 96 | config.algorithmType = currentAlgorithmType() 97 | 98 | config.midiDir = midiDirChoose.text 99 | } 100 | 101 | private fun currentAlgorithmType(): MusicAlgorithmType { 102 | return when { 103 | musicTypeRandomMinor.isSelected -> MusicAlgorithmType.RANDOM_MINOR 104 | musicTypeRandomMajor.isSelected -> MusicAlgorithmType.RANDOM_MAJOR 105 | musicTypeRandomBoth.isSelected -> MusicAlgorithmType.RANDOM_BOTH 106 | musicTypeMidi.isSelected -> MusicAlgorithmType.SEQUENTIAL 107 | else -> throw RuntimeException("Unreachable") 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/main/kotlin/intellij/music/ui/MusicKeyEventListener.kt: -------------------------------------------------------------------------------- 1 | package intellij.music.ui 2 | 3 | import com.intellij.ide.IdeEventQueue 4 | import java.awt.event.KeyEvent 5 | 6 | class MusicKeyEventListener { 7 | private val config = MusicConfig.instance 8 | 9 | fun initKeyListener() { 10 | IdeEventQueue.getInstance().addPostprocessor({ e -> 11 | if (e is KeyEvent && config.enabled && !config.onlyInEditor) { 12 | onKeyEvent(e) 13 | } 14 | false 15 | }, null) 16 | } 17 | 18 | private fun onKeyEvent(e: KeyEvent) { 19 | val keyChar = e.keyChar 20 | val keyCode = e.keyCode 21 | if (e.id == KeyEvent.KEY_PRESSED && keyChar != KeyEvent.CHAR_UNDEFINED) { 22 | val layout = if (e.keyCode == e.extendedKeyCode) { 23 | "en" 24 | } else { 25 | "ru" 26 | } 27 | 28 | val numberModifiers = getNumberModifiers(e) 29 | val event = MusicKeyboardEvent(keyChar, keyCode, layout, numberModifiers) 30 | MusicApplicationComponent.instance.keyboardPressed(event) 31 | } 32 | } 33 | 34 | private fun getNumberModifiers(e: KeyEvent): Int { 35 | var numberModifiers = 0 36 | if (e.isControlDown) ++numberModifiers 37 | if (e.isAltDown) ++numberModifiers 38 | if (e.isMetaDown) ++numberModifiers 39 | return numberModifiers 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/kotlin/intellij/music/ui/MusicKeyboardEvent.kt: -------------------------------------------------------------------------------- 1 | package intellij.music.ui 2 | 3 | data class MusicKeyboardEvent( 4 | val char: Char, 5 | val code: Int, 6 | val layout: String, 7 | val numberModifiers: Int 8 | ) -------------------------------------------------------------------------------- /src/main/kotlin/intellij/music/ui/MusicTypedHandlerDelegate.kt: -------------------------------------------------------------------------------- 1 | package intellij.music.ui 2 | 3 | import com.intellij.codeInsight.editorActions.TypedHandlerDelegate 4 | import com.intellij.openapi.editor.Editor 5 | import com.intellij.openapi.editor.EditorKind 6 | import com.intellij.openapi.project.Project 7 | import com.intellij.psi.PsiFile 8 | 9 | // https://intellij-support.jetbrains.com/hc/en-us/community/posts/206756295-TypedActionHandler-with-default-typing-behavior?flash_digest=fd9d93d7fb0d9a406689b0c9f6c765be9dca66c8 10 | class MusicTypedHandlerDelegate : TypedHandlerDelegate() { 11 | private val config = MusicConfig.instance 12 | 13 | override fun charTyped(char: Char, project: Project, editor: Editor, file: PsiFile): Result { 14 | if (config.enabled && config.onlyInEditor && editor.editorKind == EditorKind.MAIN_EDITOR) { 15 | val application = MusicApplicationComponent.instance 16 | val event = MusicKeyboardEvent(char, 0, "en", 0) 17 | application.keyboardPressed(event) 18 | } 19 | return super.charTyped(char, project, editor, file) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/kotlin/intellij/music/ui/actions/MuteAction.kt: -------------------------------------------------------------------------------- 1 | package intellij.music.ui.actions 2 | 3 | import com.intellij.openapi.actionSystem.AnActionEvent 4 | import com.intellij.openapi.actionSystem.ToggleAction 5 | import intellij.music.ui.MusicConfig 6 | 7 | class MuteAction : ToggleAction() { 8 | private val config = MusicConfig.instance 9 | 10 | override fun isSelected(e: AnActionEvent): Boolean { 11 | return !config.enabled 12 | } 13 | 14 | override fun setSelected(e: AnActionEvent, state: Boolean) { 15 | config.enabled = !state 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/kotlin/intellij/music/ui/actions/NextTrackAction.kt: -------------------------------------------------------------------------------- 1 | package intellij.music.ui.actions 2 | 3 | import com.intellij.openapi.actionSystem.AnAction 4 | import com.intellij.openapi.actionSystem.AnActionEvent 5 | import intellij.music.core.MusicAlgorithmType 6 | import intellij.music.ui.MusicApplicationComponent 7 | import intellij.music.ui.MusicConfig 8 | 9 | class NextTrackAction : AnAction() { 10 | private val config = MusicConfig.instance 11 | 12 | override fun update(event: AnActionEvent) { 13 | event.presentation.isEnabled = config.algorithmType == MusicAlgorithmType.SEQUENTIAL 14 | } 15 | 16 | override fun actionPerformed(e: AnActionEvent) { 17 | MusicApplicationComponent.instance.controller.nextTrack() 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/kotlin/intellij/music/ui/actions/SwitchToMajorScaleAction.kt: -------------------------------------------------------------------------------- 1 | package intellij.music.ui.actions 2 | 3 | import com.intellij.openapi.actionSystem.AnAction 4 | import com.intellij.openapi.actionSystem.AnActionEvent 5 | import intellij.music.core.MusicAlgorithmType 6 | import intellij.music.ui.MusicApplicationComponent 7 | import intellij.music.ui.MusicConfig 8 | 9 | class SwitchToMajorScaleAction: AnAction() { 10 | override fun actionPerformed(e: AnActionEvent) { 11 | MusicConfig.instance.algorithmType = MusicAlgorithmType.RANDOM_MAJOR 12 | MusicApplicationComponent.instance.controller.onSwitchMusicTypeAction() 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/kotlin/intellij/music/ui/actions/SwitchToMinorScaleAction.kt: -------------------------------------------------------------------------------- 1 | package intellij.music.ui.actions 2 | 3 | import com.intellij.openapi.actionSystem.AnAction 4 | import com.intellij.openapi.actionSystem.AnActionEvent 5 | import intellij.music.core.MusicAlgorithmType 6 | import intellij.music.ui.MusicApplicationComponent 7 | import intellij.music.ui.MusicConfig 8 | 9 | class SwitchToMinorScaleAction: AnAction() { 10 | override fun actionPerformed(e: AnActionEvent) { 11 | MusicConfig.instance.algorithmType = MusicAlgorithmType.RANDOM_MINOR 12 | MusicApplicationComponent.instance.controller.onSwitchMusicTypeAction() 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/kotlin/intellij/music/ui/actions/SwitchToRandomScaleAction.kt: -------------------------------------------------------------------------------- 1 | package intellij.music.ui.actions 2 | 3 | import com.intellij.openapi.actionSystem.AnAction 4 | import com.intellij.openapi.actionSystem.AnActionEvent 5 | import intellij.music.core.MusicAlgorithmType 6 | import intellij.music.ui.MusicApplicationComponent 7 | import intellij.music.ui.MusicConfig 8 | 9 | class SwitchToRandomScaleAction: AnAction() { 10 | override fun actionPerformed(e: AnActionEvent) { 11 | MusicConfig.instance.algorithmType = MusicAlgorithmType.RANDOM_BOTH 12 | MusicApplicationComponent.instance.controller.onSwitchMusicTypeAction() 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/kotlin/intellij/music/ui/actions/SwitchToSequentialAction.kt: -------------------------------------------------------------------------------- 1 | package intellij.music.ui.actions 2 | 3 | import com.intellij.openapi.actionSystem.AnAction 4 | import com.intellij.openapi.actionSystem.AnActionEvent 5 | import intellij.music.core.MusicAlgorithmType 6 | import intellij.music.ui.MusicApplicationComponent 7 | import intellij.music.ui.MusicConfig 8 | 9 | class SwitchToSequentialAction: AnAction() { 10 | override fun actionPerformed(e: AnActionEvent) { 11 | MusicConfig.instance.algorithmType = MusicAlgorithmType.SEQUENTIAL 12 | MusicApplicationComponent.instance.controller.onSwitchMusicTypeAction() 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | intellij.music 3 | Fancy Music 4 | Fancy Music 5 | 6 | 9 | 10 | com.intellij.modules.lang 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 21 | 22 | 23 | 25 | 26 | 27 | 29 | 31 | 33 | 35 | 36 | 37 | 38 | 39 | intellij.music.ui.MusicApplicationComponent 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/pluginIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 9 | 10 | 12 | 14 | 16 | 17 | 19 | 20 | --------------------------------------------------------------------------------