Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #!/bin/bash
- # midishcscript.sh
- # Download this script from: pastebin.com/rRvH5inb
- #
- # This script lets you program sequences to play with 'midish'. That sequence is saved in a file called "midishtemp" Download midish from:
- #
- # https://midish.org
- #
- # Files/scripts this script also needs
- # ====================================
- # - midishsong.sh : song/project script for midish
- # - midishchord.sh : chord script
- # - midishcreaterc.sh: add devices to .midishrc
- #
- # This script REQUIRES!:
- # ======================
- # - 'Extra RC' for midish by Jeanette C. from:
- # http://juliencoder.de/test/index.php?page=software/index
- # - My personal 'midishrc' file because it has additional midish procedures,
- # download from:
- # https://pastebin.com/NcQ3aMaA
- # (deleted due to "abuse reports" and "deemed potentially harmful"!!!???,
- # see e-mail 14-9-'24)
- #
- # Function list
- # =============
- # menu : Main menu
- # help_me : Help menu
- # other : Other menu
- # song : Song menu
- # chord : Chord menu
- # list_devices : Print in- & output devces
- # single_note : Input single note-number via Midi keyboard
- # midish_play : Play a sequence in midish
- # noteinput : Create 16 step sequence (into "midishtemp" file)
- # convertnotes : Convert Midi note-numbers to musical notes or edit them
- # connect : Connect Midi input to output for midish
- # play : Play a synth via midish
- # seqplay : Play a sequence once or in a loop in midish
- # kill_midish : Kill midish processes in the background
- # tempo : Set tempo in BPM
- # metronome : Set metronome on/off
- # chord : Go to chord script
- #
- # Files this script creates
- # =========================
- # - midishtemp : temp/work file with 16 Midi note numbers
- #
- # To do
- # =====
- # - Function 'Tempo': make external master clock device work in seq play
- # and loop
- # - Write the chord script (chord procedure is already added in .midishrc?)
- # - Metronome menu/function.
- # - Make exiting play and loop easier.
- # Return to Main Menu when pressed Ctrl C
- trap "midishscript.sh" 2
- # Check if tempo is set, if not set it to 100 BPM
- if [[ -z $tempo ]]; then
- tempo='100'
- # Save variable for other script
- export tempo
- fi
- # Check if Master clock device is set, if not set it to nil
- if [[ -z $masterdevice ]]; then
- masterdevice='nil'
- fi
- #######################
- # M A I N M E N U #
- #######################
- function menu()
- {
- # This function prints the Main menu
- # Reset trap
- trap "exit" 2
- clear #123456789012345678901234567890123456789012345678#
- #echo -e "\e[37;44;1m――――――――――――◆ M A I N M E N U ◆―――――――――――――\e[m"
- #echo -e "\e[37;44;1m :: :: :: :: M A I N M E N U :: :: :: :: \e[m"
- echo -e "\e[37;44;1m M A I N M E N U \e[m"
- echo -e "\e[1mSequence \e[0m1. Enter sequence of 16 notes/steps"
- echo " 2. [E]dit/show notes"
- echo " 3. Load & save [S]equence menu"
- echo
- echo -e "\e[1mPlayback \e[0m4. [C]onnect in- and output"
- echo " 5. [I]nteractively play synth"
- echo " 6. [P]lay or [L]oop sequence"
- echo
- echo -e "\e[1mSongs \e[m7. [S]ong menu"
- echo
- echo -e "\e[1mMisc. \e[0;34m[O] Other menu (tempo, etc.)\e[m"
- echo -e " \e[32m[H] Help\e[m"
- echo -e " \e[31m[X] Exit\e[m"
- echo -e -n "\n\e[34;1mChoice (Enter to go back to prev. menu/state): \e[0m"; read -N 1 i; echo
- case $i in
- 1 ) noteinput;;
- c|4 ) connect;;
- e|2 ) convertnotes;;
- h ) help_me;;
- i|5 ) play;;
- l|p|6) seqplay;;
- o ) other;;
- s|3|7) midishsong.sh;;
- x ) echo; exit
- esac
- }
- ######################################
- # G E N E R A L F U N C T I O N S #
- #####################################
- function list_devices()
- {
- # This function prints in- or outdevices on screen. It is called from/meant to be used in connect().
- #
- # This function NEEDS the following variables to be set:
- # $devicetype: by the function connect(): inew or onew in- or output
- # device type from .midishrc
- #
- # This function sets these varaibles:
- # $max : max amount of in- or output devices
- # $element : element in array
- #
- # Arrays used:
- # array[] : array of device names
- # Put elements in an array for awk
- max=$(cat ~/.midishrc | grep -Ec "$devicetype")
- array=(); array[0]="bogusvalue" # We can now start counting at 1 instead of 0
- # Print numbers and in- or ouput devices to choose from and build array of devices
- for (( n=1; n<=$max; n++ ))
- do
- element=$(cat ~/.midishrc | grep "$devicetype" | \
- awk 'NR=='$n' {print $2}')
- printf "%3s %s\n" $n $element
- array+=("$element")
- done
- }
- function connect()
- {
- # This function connects input to output.
- #
- # This function NEEDS the function(s):
- # list_devices: to print in- or output devices on screen
- #
- # This function uses the following array from 'list_devices':
- # array[] : array of device names
- #
- # This function sets the variables:
- # $input : name of input device in .midishrc
- # $output : name of output device in .midishrc
- clear
- # Choose input device
- devicetype='inew' # used/needed in list_devices()
- list_devices # internal function
- # The array "array" from 'list_devices' is used here:
- read -N2 -p "Choose input dev (e.g. keyboard) nr.: " input
- # Convert $input to element from array
- echo "(${array[$input]})"
- input=${array[$input]}
- # Choose output device
- devicetype='onew'
- list_devices
- read -N2 -p "Choose output dev (e.g. synth) nr.: " output
- echo "(${array[$output]})"
- output=${array[$output]}
- # Save variables for other script
- export input output
- echo -e "\e[34;1mPress enter to return to Menu\e[0m"; read
- # Loop
- menu
- }
- function single_note()
- {
- # This function returns a single Midi note-number input by Midi-keybd.
- #
- # This function is called by the functions:
- # noteinput() : enter a seq of 16 notes
- # convertnotes() : convert Midi note-numbers to musical notes
- #
- # This function NEEDS the following variable(s) to be set:
- # $portnumber: created by function noteinput()
- # Check portnumbers with: 'aseqdump -l'
- aseqdump -p $portnumber | sed \
- -e 's/ '$portnumber':0 Note on 0, note //' \
- -e 's/, velocity .*$//' \
- -e '/Note off/d; /Waiting/d; /Source/d; /Polyphonic aftertouch/d' \
- -e 's/ '$portnumber':0 Control change 0, controller 1, value 127/s /' \
- -e '/ '$portnumber':0 Control change 0, controller 1, value .*$/d' \
- -e 's/ '$portnumber':0 Pitch bend 0, value -8192/ll/' \
- -e '/ '$portnumber':0 Pitch bend 0, value -.*$/d' \
- -e 's/ '$portnumber':0 Pitch bend 0, value 8191/l /' \
- -e '/ '$portnumber':0 Pitch bend 0, value .*$/d' \
- -e 'q' \
- | sed -z 's/\n/ /'
- }
- function noteinput()
- {
- # This function will let you enter a sequence of 16 notes. The numbers: will be saved in the file 'midishtemp'. Opposed to the procedure in my personal ~/.midishc file it does not need the two 's' entries after an 'll'. (note: a 'l' long note doesn't need a [s]ilence at all)
- #
- # This function creates the file(s):
- # midishtemp : file with the 16 midi note-numbers
- #
- # This function needs the function(s):
- # single_note : returns a single Midi note-number to be saved
- #
- # This function sets the follown variables:
- # $portnumber : Midi port-number of keyboard to enter notes with
- # $max : number of notes
- # $n : counter to count the inputted notes (step)
- # $m : $n minus 1 to determine previous step
- # $i : counter to count to 2
- # Reset trap
- trap "menu" 2
- clear
- echo -e "Determine your portnumber from output below:\n"
- # Use sed to remove anything after the 40th character
- aseqdump -l | sed 's/.//40g'
- echo -e "\nInput portnumber and (re)enter your notes. Press Ctrl+C to return to the Menu if you're satisfied with the notes you've entered before."
- read portnumber
- rm midishtemp
- max=16
- echo "Steps/notes:"
- # Do for all the (16) steps
- for (( n=1; n<=$max; n++ ))
- do
- printf "[%2s]" $n;
- # Use the function single_note
- single_note | \
- tee -a midishtemp
- # Concatenate/remove line ending
- sed -i -z 's/\n/ /g' midishtemp
- # If note = ll, Then the next step is simply the note input.
- # If (step - 2) was ll then the next 2 entries are 's' (= skip)
- # and continue for n=n+2.
- m=$(($n-1)) # m is previous step
- if [[ $m -gt 0 ]]
- then
- if [[ $(echo "$(awk '{print $'$m'}' midishtemp)") == 'll' ]]
- then
- echo -n "s s " >> midishtemp
- # Print two times on screen the step and [s]ilence
- for (( i=1; i<=2; i++ ))
- do
- # If multple of 4 then new line
- if (( $n % 4 == 0 ));
- then echo
- fi
- let n++ # you can't use "just" use n++ in Bash
- printf "[%2s]" $n; printf "s "
- done
- fi
- fi
- # If multple of 4 then new line:
- if (( $n % 4 == 0 ));
- then echo
- fi
- done
- # Loop
- echo -e "\n\e[34;1mPress enter to return to Menu\e[0m"; read bogusvalue
- menu
- }
- function kill_midish()
- {
- killall midish
- echo -e "\e[34;1mPress enter to return to Menu\e[0m"; read bogusvalue
- other
- }
- #######################
- # S E Q U E N S E S #
- #######################
- function convertnotes()
- {
- # This function converts Midi note-numbers from "midishtemp"" to musical notes (to print then in human readable on screen) and offers to edit them or show the Midi note-numbers.
- #
- # This function uses the array(s):
- # notes : array containing notes, used to convert note-numbers
- #
- # This function needs the file(s):
- # midishtemp : contains the Midi note-numbers, made by noteinput().
- # midishtemptemp: awk can't do in-line editing (like sed can)
- #
- # This function sets the variable(s):
- # $max : number of enries in 'midishtemp'
- # $n : counter for the entries in 'midishtemp'
- # $entry : entry number $n in 'midishtemp'
- # $answer : edit note or not?
- # $portnumber : portnumber set in noteinput()
- # $step : step in 'midishtemp' to change
- # $note : input note by Midi keybd (by noteinput())
- #
- # This function calls the function(s):
- # single_note : input a single not with a Midi-keyboard
- # which depends on:
- # noteinput() : create 16 step sequence and set $portnumber
- #
- # To do:
- # - Get rid of the dirty workaround with -v $note in the awk command.
- # Below is an array with all music notes. Example: entry 47 is note C3, etc.:
- # 000 001 002 003 004 005 006 007 008 009 010 011 012 013 014 015 016 017 018 019 020 021 022 023 024 025 026 027 028 029 030 031 032 033 034 035 036 037 038 039 040 041 042 043 044 045 046 047 048 049 050 051 052 053 054 055 056 057 058 059 060 061 062 063 064 065 066 067 068 069 070 071 072 073 074 075 076 077 078 079 080 081 082 083 084 085 086 087 088 089 090 091 092 093 094 095 096 097 098 099 100 101 102 103 104 104 106 107 108 109 110 111 112 113 114 115 116 117 118 119
- notes=(C# D2 D# E F F# G G# A A# B C C# D2 D# E F F# G G# A A# B C1 C#1 D21 D#1 E1 F1 F#1 G1 G#1 A1 A#1 B1 C2 C#2 D2 D#2 E2 F2 F#2 G2 G#2 A2 A#2 B2 C3 C#3 D3 D#3 E3 F3 F#3 G3 G#3 A3 A#3 B3 C4 C#4 D4 D#4 E4 F4 F#4 G4 G#4 A4 A#4 B4 C5 C#5 D5 D#5 E5 F5 F#5 G5 G#5 A5 A#5 B5 C6 C#6 D6 D#6 E6 F6 F#6 G6 G#6 A6 A#6 B6 C7 C#7 D7 D#7 E7 F7 F#7 G7 G#7 A7 A#7 B7 C8 C#8 D8 D#8 E8 F8 F#8 G8 G#8 A8 A#8 B8)
- clear
- # Show notes
- max=$(awk '{print NF}' midishtemp)
- echo -e "\e[34;1mSteps/beats: \e[0m"
- for (( n=1; n<=$max; n++ ))
- do
- entry=$(awk '{print $'$n'}' midishtemp)
- # If number then convert, if letter (like 'l' for long note) then print
- if [[ $entry =~ [0-9] ]]
- then
- # Print every entry in 8 "spaces"
- #printf "[%2s]" $n; printf "%-4s" ${notes[$entry]}
- printf "[%2s]%-4s" $n ${notes[$entry]}
- else
- printf "[%2s]%-4s" $n $entry
- fi
- # If multple of 4 then new line
- if (( $n % 4 == 0 ));
- then echo
- fi
- done
- # Ask to edit notes or show note-numbers, else back to Menu
- printf "\e[34;1m\nChoose:\n\e[0m"
- printf " [e] Edit a note.\n"
- printf "[Enter] Show the array of Midi note-numbers to\n use in midish.\n"
- read -sN 1 answer
- echo
- if [[ $answer != "e" ]]
- then
- cat midishtemp
- echo -e "\e[34;1m\nPress enter to return to Menu\e[0m"; read
- menu
- fi
- # Check if port-number has been set already i.e if it is a number. If not then ask for a portnumber.
- if ! [[ $portnumber =~ ^[0-9]+$ ]]
- then
- aseqdump -l | sed 's/.//40g'
- printf "Port number of Midi keyboard: "
- read portnumber
- fi
- printf "Step to change: "
- read step
- printf "Enter note on Midi keyboard:"
- # Use the function 'single_note'. Note: that function needs the var $portnumber. If you haven't called the function 'noteinput' then that $portnumber isn't set.
- # We must use an ugly -v workaround for awk, maybe this can be done differently?
- # With awk replace one note (i.e. one field) in the only record in the file.
- awk -v note=$(single_note) '{ {$'$step'= note} print $0 }' midishtemp > midishtemptemp; mv midishtemptemp midishtemp
- # Concatenate/remove line ending:
- sed -i -z 's/\n/ /g' midishtemp
- # Loop back:
- $FUNCNAME
- }
- function play()
- {
- # This function lets one play a synth in midish "interactive" mode. Adittionally it can send midish to the background.
- #
- # This function connects previously set $input to $output (from the function connect()) and starts [i]nteractive mode in midish.
- trap "menu" 2
- function play_my_synth()
- {
- midish -b <<END
- exec "/home/$USER/.midishrc"
- exec "/home/$USER/.midish_extrarc"
- rnew $input $output
- $clock_command
- i
- END
- }
- read -N 1 -p "Do you want to direct midish to the background [y/n=Enter]? " answer; echo
- if [[ $answer == 'y' ]]
- then
- play_my_synth &>/dev/null &
- echo "Midish is now running in the backround."
- else
- play_my_synth
- fi
- echo -e "\e[34;1mPress enter to return to Menu\e[0m"; read bogusvalue
- menu
- }
- function midish_play()
- {
- # This function plays a sequence in midish.
- #
- # This function needs the following variable(s) to be set for additional
- # midish commands (loop etc.):
- #
- # $midish_command: additional midish commands
- midish -b <<-END
- print
- print "============================================================================"
- exec "/home/$USER/.midishrc"
- exec "/home/$USER/.midish_extrarc"
- print "============================================================================"
- print
- print
- rnew $input $output
- t $tempo
- sequence $seq
- tcheck
- $clock_command
- print
- print
- print
- print
- print
- print
- print
- print
- print
- print
- print
- print
- print
- print
- print
- print
- print
- print "Starting playback."
- print "Ignore the lines below stating: 'alsa_open: couldn't parse alsa port'."
- $midish_command
- p
- END
- }
- function seqplay()
- {
- # This function plays a sequence once or in a loop.
- #
- # This function NEEDS the function(s):
- # midish_play : to play a sequence
- #
- # This function defines the following variables:
- # $seq : sequence between curly braces
- # $answer : play or loop?
- #
- # This function sets the following variable(s) for other fu's:
- # $midish_command: additional commands to use in midish_play()
- # Reset trap
- trap "menu" 2
- # Check if in- and out put are set
- if [[ -z $output ]]; then
- echo "Connect in- and output first."
- read -p "Press Enter to continue..." bogusvalue
- menu
- fi
- # Place midi note-numbers between accoulades and get rid of the bleedin' line ending
- seq=$(printf "{"; sed -z 's/\n//' midishtemp; printf "}")
- # Make choice: play or loop
- read -N 1 -p "Choose [p]lay or [l]oop current sequence." answer
- echo
- if [[ $answer == 'p' ]]
- then
- #my
- midish_command=""
- midish_play
- else
- #loop
- # Test if this works:
- midish_command="sel 1; loop"
- midish_play
- fi
- # Loop back to menu
- trap "midishscript.sh" 2 # set trap back to "normal"
- echo -e "\e[34;1mPress enter to return to Menu\e[0m"; read bogusvalue
- menu
- }
- #########################
- # M E N U O T H E R #
- #########################
- function other()
- {
- # 'Other' menu.
- # Reset trap:
- trap "exit" 2
- clear
- echo "Other"
- echo "====="
- echo "1. [T]empo: Midi and Master device"
- echo "2. Set metronome on/off"
- echo "3. [K]ill midish in backround"
- echo "4. Chord menu"
- echo "5. Add Midi device to midish"
- echo
- echo "M. Main menu"
- echo -e -n "\nChoice (don't press Enter): "; read -N 1 i; echo
- case $i in
- 2) metronome;;
- 4) chord;;
- 5) midishcreaterc.sh;;
- k|3) killall midish; menu;;
- m) menu;;
- t|1) tempo
- esac
- }
- function tempo()
- {
- # This function sets the tempo and the Master device for the clock (tempo).
- #
- # This function sets the following variable(s):
- # $tempo : currenent/new tempo
- # $tempo_old : save old value of $tempo for later
- # $max : number of devices in .midishrc
- # $masterdevice : master device for the clock, used to set $clock_command
- # $new_masterdevice: new master device
- # $clock_command: used in the other script, in play_chain()
- clear
- # 1. Set tempo
- echo "Current Midi tempo: $tempo"
- #; tempo_old="$tempo" # save old tempo for later
- read -p "New tempo (press Enter to skip): " new_tempo
- # If NOT pressed Enter (ASCII \0A) then set tempo to new tempo. Else do nothing.
- # https://stackoverflow.com/questions/2612274/bash-shell-scripting-detect-the-enter-key
- if [[ $new_tempo != $'\0A' ]]; then
- tempo=$new_tempo
- # Save variable $tempo for other script
- export tempo
- fi
- # 2. Set Master clock device
- echo
- echo -e "Current Master clock device (nil means R. Pi): $masterdevice \nList of Master clock devices:"
- # Print list of midish devices
- max=$(cat ~/.midishrc | grep -Ec "dnew")
- for (( n=1; n<=$max; n++ ))
- do
- # Use awk twice: first with the standard [F]ield separator <space> for the device number and then with " as FS for the device name.
- cat ~/.midishrc | grep "dnew" | awk 'NR=='$n' {printf "Device %s = ", $2}'
- cat ~/.midishrc | grep "dnew" | awk -F'"' 'NR=='$n' {printf "%s\n", $2}'
- done
- read -p "New Master clock device (press Enter to skip): " new_masterdevice
- # If NOT pressed Enter (ASCII \0A) then set new master device, else skip. Also check if device number is correct because midish won't work w/ a wrong dev.nr.
- # Midish bug? In batch (-b) mode you cannot set 'dclkrx nil' like in normal mode. So IF $masterdevice is nil THEN ...
- if [[ $new_masterdevice != $'\0A' && $new_masterdevice -lt $max ]]; then
- masterdevice=$new_masterdevice
- # Convert $masterdevice into $clock_command
- clock_command="dclkrx $masterdevice"
- # Save variable for other script
- export clock_command
- elif [[ $new_masterdevice -gt $((max-1)) ]]; then
- echo "Wrong device number entered. Try again."; read -p "Press Enter to continue..."
- # Loop
- tempo
- fi
- # Loop
- read -p "Press Enter to continue..." bogusvalue
- menu
- }
- function metronome()
- {
- echo "WiP"
- }
- function chord()
- {
- # WiP ...
- midishchord.sh
- # 1. Enter chord(s)
- # 2. Chain chords into song (in separate midish track?)
- # 3. Can I use an existing functions or write (a whole lotta) new ones?
- }
- ###################
- # H E L P M E #
- ###################
- function help_me()
- {
- clear
- echo -e "\e[32m\
- 1. This Bash script is meant to be used as a simple old school sequencer. You create bars of 16 steps and chain those into songs. This script uses 'midish' from https://midish.org .
- 2. Make sure that you've defined your Midi hardware in midish's config file ~.midishrc!
- 3. This script also uses the 'midish_extrarc' file from http://juliencoder.de/test/index.php?page=software/index .
- 4. When entering notes for a sequence you can push the mod wheel all the way up and back again to enter a [s]ilence/skip. You can push the pitch wheel all the way up to enter a [l]ong note (of 2 steps) after the 'l'. Or you can push the pitch wheel all the way down to enter a very [ll]ong note (of 4 steps/one beat) after the 'll'.
- \e[0m" | more -d
- echo -e "\e[34;1mPress enter to return to Menu\e[0m"; read bogusvalue
- menu
- }
- ###################################
- # S T A R T O F S C R I P T #
- ###################################
- # For use in the other script. If you start this script w/ the commandline option -c then it jumps to the function 'connect'.
- if [[ $1 == '-c' ]]; then
- connect
- fi
- menu
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement