Skip to content

Instantly share code, notes, and snippets.

@Visuelle-Musik
Created December 8, 2009 20:41
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save Visuelle-Musik/251981 to your computer and use it in GitHub Desktop.
Save Visuelle-Musik/251981 to your computer and use it in GitHub Desktop.
MIDI driven "VJ"-application in ruby-processing, 'manual' see "visualSynthSettings14.rb"
# --- parameters to be set in ./data/java_args.txt ---
# -Xms256m -Xmx256m # Heap at least 256 MB
# -Djna.library.path="/ruby-processing/library/gsvideo/library/gstreamer/win" # optional for future video extension...
require "jrMidiVsynth22" # class JRmidi for basic MIDI I/O in JRuby, adapt your MIDI-ports here if necessary
SETTINGS = "visualSynthSettings14" # contains changable settings for "which function to call" by modifier-keys
# === helper functions === (map internal representation of layers to external for "print-out"
class Range
def inspect_plus_one
"Layer " + self.map{|lay|lay+1}.inspect.sub(/\[/,"").sub(/\]/,"")
end
end
class Array
def inspect_plus_one
"Layer " + self.map{|lay|lay+1}.inspect.sub(/\[/,"").sub(/\]/,"")
end
end
class Footage # provide names of footage to be used in sketch plus a helper function to decide to put on which key...
NOTE_NAMES = %w[C C# D D# E F F# G G# A A# H]
attr_reader :media_names, :notes
def initialize
@media_names = Array.new(128).map!{[]} # 128 program_changable directory-entries of media...
@notes = []
get_media()
get_notes()
end
private
def get_media # get list of picture-files beginning with "nnn" with path
Dir.foreach("./pic") { |dir_name| # "root-directory" is named "pic", change here if you want to structure your footage differntly!
if( dir_name =~ /.*\d+$/ ) # directory of type xyz-1 or xyz-002 for instance
dirIdx = dir_name[/(.*)(\d+)/,2].to_i-1 # extract numbers at end of name, numbers begin from 1 to correlete with program-change, internally beginning from 0...
Dir.foreach("./pic/"+dir_name) { |file_name|
if( file_name =~ /^\d+.*/ ) # filename beginning with numbers
fileIdx = file_name[/^(\d+)(.*)/,1].to_i # extract numbers at beginning of name
@media_names[dirIdx][fileIdx] = "./pic/" + dir_name + '/' + file_name
end
}
end
}
@media_names
end
def get_notes # helper-function to get list of keys to be associated with the footage...
octave = -3
for i in (0..127)
octave +=1 if (i%12 == 0)
@notes << "%3.3d_%s%d" % [i, NOTE_NAMES[i%12], octave]
end
@notes
end
end # class Footage
class Oskar < Processing::App
require SETTINGS # 'include' the settings not until here, so we can use Oskar::-name-scope
load_library 'opengl'
include_package "processing.opengl"
load_library 'video'
include_package "processing.video" # 20091116
MOVIE_MAKER_DEFAULTS = { :codec => MovieMaker::MOTION_JPEG_B, :quality => MovieMaker::MEDIUM } # adopt settings for movie-record here if needed!
# type Either ANIMATION, BASE, BMP, CINEPAK, COMPONENT, CMYK, GIF, GRAPHICS, JPEG, MS_VIDEO, MOTION_JPEG_A, MOTION_JPEG_B, RAW, SORENSON, VIDEO, H261, H263, H264
# quality Either WORST, LOW, MEDIUM, HIGH, BEST, LOSSLESS
attr_accessor :bottomLayer, :ceilingLayer
ROTATE_FACTOR = TWO_PI / 360.0
ROTATE_FACTOR_CONTROLLER = 360.0 / 126 # pitch full down is 0 degrees, middle 180, full up 360 degrees - for controller
def initialize(args)
@mm = nil # "hook" for "MovieMaker" to record animation lateron...
@mm_params = nil # parameters for MovieMaker-init...
@oskar_frame_rate = 25 # framerate for animation _and_ movie-record...
@media = Array.new(128).map!{[]} # media[][] up to 128*128 medias to be switched, depends on your Graphic-Card-Memory and picture-size, <= 64 recommended
@footage = Footage.new
@media_names = @footage.media_names
@note_names = @footage.notes
@ctrl = Array.new(8).map{ Array.new(128) } # Controller-Array for (128) controllers on 7+1 Layers...
# --- initial settings not be be reset automatically later: ---
# --- set by bank-select and programm-change:..
@bottomLayer = 0 # first active Layer (layers are from 1 to 7, numbered 0 to 6...
@ceilingLayer = 1 # last active Layer (layers are from 1 to 7, numbered 0 to 6..
(@prg ||= [nil]*8).map! {0} # set Media-Directory-ID to first directory, "pics001" or "movies001" for instance...
# --- standard-mapping to keys on media-channels
(@pic_idx ||= [nil]*8).map! {0} # set Picture-ID to first picture on all layers so far
@mm_record = false # movie recording not started yet...
get_initials() # resolution and verbose y/n...
default_var_values() # load defaults, will be called again when "reset all notes/controllers" is used
initial_settings() # init to initial settings as defined in "visiualSynthSettings"
super(args.merge( @inital_resolution_hash )) # call Jeremy Jashkenas' ruby-processing "constructor"...
end
def default_var_values # called once on startup by initialize() and later again on reset_all() by "all notes off" or explicitly be key-code if set by user!
# --- exclusively set by modifier-keys or "all notes off" ---
@background = [0,0,0]
@ctrl.each {|ctrl| ctrl.map! {254}} # set all 128 controllers on all 7 layers to "max" (assume value is nil, so instanciated only when called for the first time, but values will be reset eachtime called again...)
# --- use ||= operator, so that this function can be called later again without changing the references used by MIDI-handlers in jrMidiVsynth-file...
(@blend ||= [nil]*8).map! {nil} # not a standard setter, because global-effect behaves different -> no additional blend-effect to be called, so far (on all layers)...
(@filter ||= [nil]*8).map! {nil} # not a standard setter, because global-effect behaves different -> no additional filter-effect to be called, so far (on all layers)...
# --- internally used 2D-geometrics...
(@pos_x ||= [nil]*8).map! {0} # x-position of image relative top/left corner..., used internally by scale and alike...
(@pos_y ||= [nil]*8).map! {0} # y-position of image relative top/left corner..., used internally by scale and alike...
(@size_x ||= [nil]*8).map! {@width} # initial/standard width of image
(@size_y ||= [nil]*8).map! {@height} # initial/standard height of image
# === variables for user-access start here: ===
(@echo ||= [nil]*8).map! {false} # echo keys and their function pressed on modifier-layers or not...
(@base_x ||= [nil]*8).map! {0} # x-position of canvas relative top/left corner...,
(@base_y ||= [nil]*8).map! {0} # y-position of canvas relative top/left corner...,
(@velocity_to_transparency ||= [nil]*8).map! {false} # pics will respond to velocity with alpha when triggered...
(@velocity_to_size ||= [nil]*8).map! {false} # pics will respond to velocity with size when triggered...
(@velocity_to_rotate ||= [nil]*8).map! {false} # pics will respond to velocity with z-rotation when triggered...
(@allow_note_off ||= [nil]*8).map! {false} # note off triggers "black" or not...
(@geometry_on ||= [nil]*8).map! {false} # allow rotate x/y/z or not / switch off by false...
(@layer_on ||= [nil]*8).map! {false} # All Layers off, set Layer 1 and 2 on in global setting for instance
(@effect_on ||= [nil]*8).map! {false} # switch per layer if or if not effects are to be applied (performance optimisation in draw()-main-loop!
(@filter_before_blend ||= [nil]*8).map! {false} # default effect order: blend, than filter, if any (caution: performance may be slow)
# --- normally set by pitchange... ---
(@coord_x ||= [nil]*8).map! {0} # x-position of image relative top/left corner of "canvas" for current layer
(@coord_y ||= [nil]*8).map! {0} # x-position of image relative top/left corner of "canvas" for current layer
(@coord_x_factor ||= [nil]*8).map! {1.0} # scale "canvas-width" of current layer to 100 %
(@coord_y_factor ||= [nil]*8).map! {1.0} # scale "canvas-height" of current layer to 100 %
(@size_x_factor ||= [nil]*8).map! {1.0} # scale "image-width" of current layer to 100 %
(@size_y_factor ||= [nil]*8).map! {1.0} # scale "image-height" of current layer to 100 %
(@rotate_x ||= [nil]*8).map! {0.0} # rotate-value in degrees 0-360 (or even bigger or negative)...
(@rotate_y ||= [nil]*8).map! {0.0} # rotate-value in degrees 0-360 (or even bigger or negative)...
(@rotate_z ||= [nil]*8).map! {0.0} # rotate-value in degrees 0-360 (or even bigger or negative)...
end
def load_media(verbose)
for bank in (0..127)
for id in (0..127)
if( media_name = @media_names[bank][id] )
@media[bank][id] = loadImage(media_name)
puts "%2.2d|%-8.8s %s %s" % [bank+1, @note_names[id], media_name, @media[bank][id].inspect] if verbose
end
end
end
end
def get_initials # init to initial settings as defined in "visualSynthSettings"
if ( p=VisualSynth::initial_settings.rassoc(:set_initials) )
send( p[1], p[0], 0, p[2] ) # for instance: set_initials((8..8), 0, ':width => 720, :height => 576, :verbose => true')
else
set_initials((8..8), 0, ':width => 800, :height => 600, :verbose => true' ) # use defaults if not set...
end
if ( p=VisualSynth::initial_settings.rassoc(:set_midi_ports) )
res = send( p[1], p[0], 0, p[2] ) # for instance: set_midi_ports((8..8), 0, 'port_1 => 0, port_2 => 1') to use the first two MIDI-in-ports...
set_midi_ports((8..8), 0, ':port_1 => 0' ) if !res # no port-settings found, use default!
else
set_midi_ports((8..8), 0, ':port_1 => 0' ) # use default if not set...
end
if ( p=VisualSynth::initial_settings.rassoc(:enable_movie_record) )
send( p[1], p[0], 0, p[2] ) # initialize MovieMaker settings...
end
end
def initial_settings # init to initial settings as defined in "visiualSynthSettings"
VisualSynth::initial_settings.each do |param|
if( sym = param[1] ) # format-example: [(0..1), :set, :@layer_on, true ]
next if [:set_initials, :set_midi_ports, :enable_movie_record].include?(sym) # these values can only be set once, initially => ignore!
layer_range = param[0].map {|lay| lay-1 } # translate from external representation of layerrange (1..7) to internal array [0..6]
if( sym != :set ) # special setter-routine or one function to be called?
send( sym, layer_range, 0, param[2] ) # layer_range=param[0], sym, data2=0, value=param[2]
else # standard setter-routine for "layer-big" array of instance-variables in main-loop...
if( param[2].class == Array ) # check if params are 2 individual ones or an array of two or more settings to be done...
paramArray = param[2]
else
paramArray = [ [param[2], param[3]] ]
end
dispatch( layer_range, note=0, paramArray ) # transcode external layer-index to internal, decide which instance-variables to be set in "main-loop"...
end
end # else: given setting not valid (nil) or not implemented, try next one in setting array or setting array until it's done...
end
end
# === ruby-processing setup-routine, will be called directly before rendering-loop draw()... ===
def setup
render_mode(OPENGL) # caution: switching rendermode will exit from setup => leave this statement on top!
# hint(ENABLE_OPENGL_4X_SMOOTH) # standard is 2X Smooth (anti-aliasing), switch on if images are of inferior quality
hint(DISABLE_DEPTH_TEST) # ! switch off OpenGL Z-order-testing... needed for X-rotation for instance, so that it is not "cut" by the layers underneath
# --- Use 4 MIDI-IN-Ports to get enough bandwith (Baudrate) and several extra pitch-bend (14 bit) controllers
# Ports will be handled identically, exept of pitchbend which will be "port-selective...", see documentation in "visualSynthSettings"-file
params = {
:port_1 => @port_1, :port_2 => @port_2, :port_3 => @port_3, :port_4 => @port_4, :velocity_to_rotate => @velocity_to_rotate,
:ctrl => @ctrl, :idx => @pic_idx, :prg => @prg, :note_names => @note_names, :allow_note_off => @allow_note_off,
:width => @width, :height => @height, :velocity_to_transparency => @velocity_to_transparency, :velocity_to_size => @velocity_to_size,
:pos_x => @pos_x, :pos_y => @pos_y, :base_x => @base_x, :base_y => @base_y, :background => @background,
:coord_x => @coord_x, :coord_y => @coord_y, :coord_x_factor => @coord_x_factor, :coord_y_factor => @coord_y_factor,
:size_x => @size_x, :size_y => @size_y, :size_x_factor => @size_x_factor, :size_y_factor => @size_y_factor,
:rotate_x => @rotate_x, :rotate_y => @rotate_y, :rotate_z => @rotate_z, :echo => @echo, :verbose => @verbose
}
JRmidiCtrl.new( self, @port_1, params ) if @port_1
JRmidiCtrl.new( self, @port_2, params ) if @port_2
JRmidiCtrl.new( self, @port_3, params ) if @port_3
JRmidiCtrl.new( self, @port_4, params ) if @port_4
load_media(@verbose) # show medianames, relvant keys, banks and Java-Object-IDs if verbose == true
colorMode(RGB, r=255, g=255, b=255, a=255 ) # Red green and alpha for MIDI-controllers, value 0..254, because we use 0..127*2...
frameRate( @oskar_frame_rate ); # default is 60, set to 25 for monitors with 75 Hz, for instance...
puts "frameRate: " + @oskar_frame_rate.to_s
# --- initialize recording of "movie" if desired... ---
if @mm_params
# for example params == { :activate => true, :overwrite => true, :name => "oskar.mov", :codec => MovieMaker::MOTION_JPEG_B, :quality => MovieMaker::MEDIUM }
movie_framerate = @mm_params[:framerate] ? @mm_params[:framerate] : @oskar_frame_rate # take framerate from movie-setting if given, else take animation-framerate...
@mm = MovieMaker.new( self, width, height, @mm_params[:name], movie_framerate, @mm_params[:codec], @mm_params[:quality] )
end # for example: @mm = MovieMaker.new(self, width, height, "oskar.mov", 25, MovieMaker::MOTION_JPEG_B, MovieMaker::MEDIUM)
end
def blend_adjust( layer, effect_params ) # adjust relative (0.0-1.0) blendparams to their current "real" values...
blend = []
blend[0] = effect_params[0] * @width # relative x-offset destination, ("canvas")
blend[1] = effect_params[1] * @height # relative y-offset destination, ("canvas")
blend[2] = @width * effect_params[2] # relative x-size destination, ("canvas")
blend[3] = @height * effect_params[3] # relative y-size destination, ("canvas")
blend[4] = effect_params[4] * @width + @base_x[layer]+@coord_x[layer]+@pos_x[layer] # relative x-offset source, ("image")
blend[5] = effect_params[5] * @height + @base_y[layer]+@coord_y[layer]+@pos_y[layer] # relative y-offset source, ("image")
blend[6] = @size_x[layer] * effect_params[6] # relative x-size source, ("image")
blend[7] = @size_y[layer] * effect_params[7] # relative y-size source, ("image")
blend[8] = effect_params[8] # blendmode
blend
end
# === main ruby-processing loop, will be called each frame, depending on framerate... ===
def draw
background(*@background) # start each cycle with a black background... MIDI-channels 1,3,5... where media (pictures) + ctrls are on, background-color may be changed...
for layer in (@bottomLayer..@ceilingLayer)
if( @layer_on[layer] && (idx=@pic_idx[layer]) ) # display layer if active and not already note-off for current picture...
if( pic=@media[@prg[layer]][idx] ) # check for pic and remember it
tint(@ctrl[layer][1], @ctrl[layer][2], @ctrl[layer][3], @ctrl[layer][4])
if( @geometry_on[layer] )
x_position = @base_x[layer]+@coord_x[layer]+@pos_x[layer]
y_position = @base_y[layer]+@coord_y[layer]+@pos_y[layer]
pushMatrix() # transform operation for current layer only - begin
translate( x_trans=x_position+@size_x[layer]/2.0, y_trans=y_position+@size_y[layer]/2.0 ) # shift coordinates to middle of pic
puts "rotate --- geometry ========="
rotateX( @rotate_x[layer] * ROTATE_FACTOR )
rotateY( @rotate_y[layer] * ROTATE_FACTOR )
rotateZ( @rotate_z[layer] * ROTATE_FACTOR )
translate( -x_trans, -y_trans ) # "loadUnitMatrix"
image(pic, x_position, y_position, @size_x[layer], @size_y[layer] )
popMatrix() # transform operation for current layer only - end
else
image(pic, @base_x[layer]+@coord_x[layer]+@pos_x[layer], @base_y[layer]+@coord_y[layer]+@pos_y[layer], @size_x[layer], @size_y[layer] )
end
# --- effects (if any active) start here, may be slow, depending on CPU and RAM, some (especially in current processing-implementation filter-effects) are not OpenGL-based ---
if ( @effect_on[layer] ) # switch per layer if or if not effects are to be applied (performance optimisation in draw()-main-loop!
if( @filter_before_blend[layer] ) # effects: filter, than blend or the other way around?
filter( *@filter[layer] ) if @filter[layer] # add filter-effect if any, for instance:filter(BLUR, 6), check again if active as might be set asynchronly
blend( pic, *blend_adjust(layer, @blend[layer]) ) if @blend[layer] # add blend-effect if any, for instance: blend(img, 0, 0, 33, 100, 67, 0, 33, 100, LIGHTEST), check again if active as might be set asynchronly
else # apply blend-, then filter-effect!
blend( pic, *blend_adjust(layer, @blend[layer]) ) if @blend[layer] # add blend-effect if any, for instance: blend(img, 0, 0, 33, 100, 67, 0, 33, 100, LIGHTEST), check again if active as might be set asynchronly
filter( *@filter[layer] ) if @filter[layer] # add filter-effect if any, for instance:filter(BLUR, 6), check again if active as might be set asynchronly
end
end
end
end
end
@mm.addFrame() if @mm_record # add one frame to video-recording, if enabled before...
end
# === "callback"-functions as designed in VisualSynth::settings(), triggered depending on note, first the dispatcher is called to decide ===
# --- :set-function(s): as standard-setter for [array of] instance-variable[s] on layer[s] this "dispatcher" will be used ---
def dispatch( layer_range, note, var_sym_val ) # puts "layer_range %s set to: %s" % [layer_range.inspect_plus_one, var_sym_val.inspect]
var_sym_val.each { |var|
if(!var[0])
puts "(%s) %s: '%s' can not be set - check for missing colon or spelling !" % [layer_range.inspect_plus_one, @note_names[note], var[0]] if (@verbose || @echo[7])
return
end
instance_var = instance_variable_get(var[0])
if(!instance_var)
puts "(%s) %s: '%s' can not be set - check spelling !" % [layer_range.inspect_plus_one, @note_names[note], var[0]] if (@verbose || @echo[7])
return
end
val = var[1]
puts "(%s) set %s to %s" % [layer_range.inspect_plus_one, var[0], var[1]] if @echo[7] # check for echon on/off on global mod-layer
layer_range.each{ |layer| instance_var[layer] = val }
}
end
# --- alternative way to call setter-variables (for single variables only, in contrast to ":set-logic", where array of values can be accessed), a bit slower, maybe... ---
def method_missing( method_name, *args ) # Ruby-standard "hook" to implement a missing function during runtime
# puts "Called #{method_name} with args: #{args * ', '}"
sym = instance_eval(":@"+method_name.to_s[/_.*/][1..-1]) # strip any prefix, like "set_", add instance-variable identifier, "make" symbol for it
dispatch( args[0], 0, [[sym, args[2]]] ) # call setter-function for instance-variable(array), we have to put this as an array of arrays, because original setters can come as multiples
end
# === non-standard-callback methods start here... ===
# --- RGB A ---
def set_rgb( layer_range, data2, val )
r,g,b = val # switch colors on all currently active layers...
puts "(%s) set_rgb to %d, %d, %d" % [layer_range.inspect_plus_one, r,g,b] if @echo[7]
layer_range.each {|layer| @ctrl[layer][1]=r; @ctrl[layer][2]=g; @ctrl[layer][3]=b }
end
def set_alpha( layer_range, data2, val )
puts "(%s) set_alpha to %d" % [layer_range.inspect_plus_one, val] if @echo[7]
layer_range.each {|layer| @ctrl[layer][4] = val }
end
def set_base_coords( layer_range, data2, coords )
x, y = coords
puts "(%s) set_base_coords to x=%1.2f, y=%1.2f" % [layer_range.inspect_plus_one, x, y] if @echo[7] # check for echon on/off on global mod-layer
layer_range.each {|layer| @base_x[layer] = (x * @width).floor; @base_y[layer] = (y * @height).floor }
end
# --- transfor size or x/y position of layer[s] from velocity of note triggering ---
def set_canvas_size( layer_range, velocity, val ) # transform velo to size of "canvas"
x_size, y_size = val
layer_range.each { |layer|
@size_x[layer] = x_res = (x_size * @width * @size_x_factor[layer]).floor # max-range: 1.27 * factor (with controllers also max 1.27) * width...
@pos_x[layer] = (@width - x_res)/2
@size_y[layer] = y_res = (y_size * @height * @size_y_factor[layer]).floor # max-range: 1.27 * factor (with controllers also max 1.27) * width...
@pos_y[layer] = (@height - y_res)/2
}
puts "(%s) set_canvas_size to %s" % [layer_range.inspect_plus_one, val.inspect] if @echo[7]
end
def set_rgb_background( layer_range, data2, val ) # background-color - set be corresponding note...
puts "(%s) set_rgb_background to %s" % [layer_range.inspect_plus_one, val.inspect] if @echo[7]
r = val[0] || 0
g = val[1] || 0
b = val[2] || 0
@background[0..2] = r,g,b # keep object, so to be filled by controllers - possible option for alpha, no advandage currently: @background = val.dup << (data2 << 1) # RGB + velocity as alpha!
end
# -- transfer R, G, B or alpha of layer[s] from velocity of note triggering ---
def set_r_from_velo( layer_range, velocity, val=nil ) # transform velo to R,G,B or A...
layer_range.each {|layer| @ctrl[layer][1] = velocity*2 }; puts "(%s) set_r_from_velo to %s" % [layer_range.inspect_plus_one, velocity*2] if @echo[7]
end
def set_g_from_velo( layer_range, velocity, val=nil ) # transform velo to R,G,B or A...
layer_range.each {|layer| @ctrl[layer][2] = velocity*2 }; puts "(%s) set_g_from_velo to %s" % [layer_range.inspect_plus_one, velocity*2] if @echo[7]
end
def set_b_from_velo( layer_range, velocity, val=nil ) # transform velo to R,G,B or A...
layer_range.each {|layer| @ctrl[layer][3] = velocity*2 }; puts "(%s) set_b_from_velo to %s" % [layer_range.inspect_plus_one, velocity*2] if @echo[7]
end
def set_a_from_velo( layer_range, velocity, val=nil ) # transform velo to R,G,B or A...
layer_range.each {|layer| @ctrl[layer][4] = velocity*2 }; puts "(%s) set_a_from_velo to %s" % [layer_range.inspect_plus_one, velocity*2] if @echo[7]
end
# -- transfer R, G, B for background from velocity of note triggering ---
def set_r_bg_from_velo( layer_range, velocity, val=nil ) # transform velo to R,G,B of background
@background[0] = velocity*2; puts "(%s) set_r_bg_from_velo to %s" % [layer_range.inspect_plus_one, velocity*2] if @echo[7]
end
def set_g_bg_from_velo( layer_range, velocity, val=nil ) # transform velo to R,G,B of background
@background[1] = velocity*2; puts "(%s) set_g_bg_from_velo to %s" % [layer_range.inspect_plus_one, velocity*2] if @echo[7]
end
def set_b_bg_from_velo( layer_range, velocity, val=nil ) # transform velo to R,G,B of background
@background[2] = velocity*2; puts "(%s) set_b_bg_from_velo to %s" % [layer_range.inspect_plus_one, velocity*2] if @echo[7]
end
# --- switch picture(s) by velocity ---
def set_pic_idx_from_velo( layer_range, velocity, val=nil ) # switch picture from velocity
layer_range.each {|layer| @pic_idx[layer] = velocity }; puts "(%s) set_pic_idx_from_velo to %d" % [layer_range.inspect_plus_one, velocity] if @echo[7]
end
# --- rotate by velocity ---
def set_rotate_x_from_velo( layer_range, velocity, val=nil ) # rotate-x from velocity (velocity-1 => 0..126)
layer_range.each {|layer| @rotate_x[layer] = (velocity-1) * ROTATE_FACTOR_CONTROLLER }; puts "(%s) set_rotate_x_from_velo to %f" % [layer_range.inspect_plus_one, velocity * ROTATE_FACTOR_CONTROLLER] if @echo[7]
end
def set_rotate_y_from_velo( layer_range, velocity, val=nil ) # rotate-y from velocity
layer_range.each {|layer| @rotate_y[layer] = (velocity-1) * ROTATE_FACTOR_CONTROLLER }; puts "(%s) set_rotate_y_from_velo to %f" % [layer_range.inspect_plus_one, velocity * ROTATE_FACTOR_CONTROLLER] if @echo[7]
end
def set_rotate_z_from_velo( layer_range, velocity, val=nil ) # rotate-z from velocity
layer_range.each {|layer| @rotate_z[layer] = (velocity-1) * ROTATE_FACTOR_CONTROLLER }; puts "(%s) set_rotate_z_from_velo to %f" % [layer_range.inspect_plus_one, velocity * ROTATE_FACTOR_CONTROLLER] if @echo[7]
end
# --- transfer position and/or size or x/y position of layer[s] from velocity of note triggering ---
def set_canvas_scale_from_velo( layer_range, velocity, val=nil ) # transform velo to (centered) x+y-size of "canvas"
val = (velocity-1) * 0.01 # 126 becomes 1.26 and so on...
layer_range.each { |layer|
@size_x[layer] = x_res = (val * @width * @size_x_factor[layer]).floor # max-range: 1.27 * factor (with controllers also max 1.27) * width...
@pos_x[layer] = (@width - x_res)/2
@size_y[layer] = y_res = (val * @height * @size_y_factor[layer]).floor # max-range: 1.27 * factor (with controllers also max 1.27) * width...
@pos_y[layer] = (@height - y_res)/2
}
puts "(%s) set_canvas_scale_from_velo to %s" % [layer_range.inspect_plus_one, val.inspect] if @echo[7]
end
def set_canvas_x_size_from_velo( layer_range, velocity, val=nil ) # transform velo to (centered) x-size of "canvas"
val = (velocity-1) * 0.01 # 126 becomes 1.26 and so on...
layer_range.each { |layer|
@size_x[layer] = x_res = (val * @width * @size_x_factor[layer]).floor # max-range: 1.27 * factor (with controllers also max 1.27) * width...
@pos_x[layer] = (@width - x_res)/2
}
puts "(%s) set_canvas_x_size_from_velo to %s" % [layer_range.inspect_plus_one, val.inspect] if @echo[7]
end
def set_canvas_y_size_from_velo( layer_range, velocity, val=nil ) # transform velo to (centered) y-size of "canvas"
val = (velocity-1) * 0.01 # 126 becomes 1.26 and so on...
layer_range.each { |layer|
@size_y[layer] = y_res = (val * @height * @size_y_factor[layer]).floor # max-range: 1.27 * factor (with controllers also max 1.27) * width...
@pos_y[layer] = (@height - y_res)/2
}
puts "(%s) set_canvas_y_size_from_velo to %s" % [layer_range.inspect_plus_one, val.inspect] if @echo[7]
end
def set_x_coord_from_velo( layer_range, velocity, val=nil ) # transform velo to x-pos of "canvas"
val = (velocity-1) * 0.01 # 126 becomes 1.26 and so on...
layer_range.each {|layer|
@coord_x[layer] = ( val * @width * @coord_x_factor[layer] ).floor
}
puts "(%s) set_x_coord_from_velo to %s" % [layer_range.inspect_plus_one, val.inspect] if @echo[7]
end
def set_y_coord_from_velo( layer_range, velocity, val=nil ) # transform velo to y-pos of "canvas"
val = (velocity-1) * 0.01 # 126 becomes 1.26 and so on...
layer_range.each {|layer|
@coord_y[layer] = ( val * @height * @coord_x_factor[layer] ).floor
}
puts "(%s) set_y_coord_from_velo to %s" % [layer_range.inspect_plus_one, val.inspect] if @echo[7]
end
# --- define active layers, normally called by bank-select hi/low... ---
def set_layer_range( layer_range, data2, val ) # set bottom and ceiling of layers to show, val is given as range here, example: [(7..7), :set_layer_range, (0..1) ] (first param unused here!)
puts "(%s) set_layer_range to %s" % [layer_range.inspect_plus_one, val.inspect] if @echo[7]
@bottomLayer = val.first-1; @ceilingLayer = val.last-1 # transform external representation of index, ;puts "layer range: (%d..%d)" % [@bottomLayer, @ceilingLayer]
end
# --- effects ---
def reset_effects( layer_range=(0..7), data2=0, effect_params=nil ) # may be called by default-method or without params internally...
puts "(%s) reset_effects()" % layer_range.inspect_plus_one if @echo[7]
@effect_on.map! {false}; @blend.map! {nil}; @filter.map! {nil};
end
def set_blend( layer_range, data2, effect_params ) # example of effect_params: [0, 0, width, height, 0, 0, width, height, 2] -> blendmode ADD for entire image
layer_range.each {|layer|
@effect_on[layer] = effect_params || @filter[layer] # performance-optimization for "main loop", to only check if any effect to be done...
@blend[layer] = effect_params # global modifier channel puts effects on topmost layer, (may be nil to turn off!) remember array of effect-params, including function to be called or set to "nothing" if no function given...
}
puts "(%s) set_blend %s" % [layer_range.inspect_plus_one, effect_params.inspect] if @echo[7]
end # caution: some blend-modes are slow!
def set_filter( layer_range, data2, effect_params ) # example of effect_params: [11, 6] -> BLUR with intensity 6...
@effect_on[layer=layer_range.last] = effect_params || @blend[layer] # performance-optimization for "main loop", to only check if any effect to be done...
@filter[layer] = effect_params # global modifier channel puts effects on topmost layer, (may be nil to turn off!) remember array of effect-params, including function to be called or set to "nothing" if no function given...
puts "(%s) set_filter %s" % [layer_range.inspect_plus_one, effect_params.inspect] if @echo[7]
end # caution: some filters are very slow, currently filters result in an output as "topmost", regardless where they are put => seemingly filters are added in RAM after OpenGL rendering...
def set_effect_on_off( layer_range, velocity, effect_params=nil ) # provide current 'effect on' at note-off / 'effect off' at note-off...
layer_range.each {|layer|
@effect_on[layer] = velocity # use velocity as effect-on/off indicator...
}
puts "(%s) set_effect_on_off (velo: %s) %s" % [layer_range.inspect_plus_one, velocity.inspect, effect_params.inspect] if @echo[7]
end
# === reset_all-function: automatically called by "all notes off", may be called ecplicitly by key-press, too... ===
def reset_all( layer_range=(0..7), data2=0, val=255 ) # "panic-button" ;-)
puts "(%s) reset_all()" % layer_range.inspect_plus_one if @echo[7]
default_var_values() # same as set by initalize() at program-start!
initial_settings() # init to initial settings as defined in "visiualSynthSettings"
end
# === set resolution, framerate, verbose-mode and midi-port configuration once at startup, given in "visuaSynthSettings" (if not, use defaults! ===
def set_initials( layer_range, data2, paramstr )
@inital_resolution_hash = instance_eval( '{' + paramstr + '}' ) # for instance: {:width => 720, :height => 576}
@width, @height, @oskar_frame_rate, @verbose = @inital_resolution_hash[:width], @inital_resolution_hash[:height], @inital_resolution_hash[:framerate], @inital_resolution_hash[:verbose]
if(!@width) # use default if not set
@width = 800
@inital_resolution_hash.merge!({:width => 800})
end
if(!@height)
@height = 600 # use default if not set
@inital_resolution_hash.merge!({:height => 600})
end
@oskar_frame_rate = 25 if (@oskar_frame_rate==nil) # use default if not set
@verbose = true if (@verbose==nil) # use default if not set
end
def set_midi_ports( layer_range, data2, paramstr )
return nil if !(p = instance_eval( '{' + paramstr + '}' )) # for instance: {port_1 => 0}
@port_1, @port_2, @port_3, @port_4 = p[:port_1], p[:port_2], p[:port_3], p[:port_4]
@port_1 || @port_2 || @port_3 || @port_4 # return nil if no port found...
end
# === start/pause/stop "movie-recording" by key-press if desired... ===
def enable_movie_record( layer_range, data2, paramstr )
# for example params == { :activate => true, :overwrite => true, :name => "oskar.mov", :codec => MovieMaker::MOTION_JPEG_B, :quality => MovieMaker::MEDIUM }
begin
params = instance_eval( '{' + paramstr + '}' )
if params[:overwrite]
File.delete( params[:name] )
File.delete( params[:name] + ".#res" ) # internal file for JQT...
end
rescue # avoid "file not found" error when trying to overwrite not existing file...
end
@mm_params = params if params[:activate]
end
def start_movie_record( layer_range=nil, velocity=nil, val=nil )
puts "start movie..." if @verbose
@mm ? (@mm_record = true) : puts( "start: Movimaker not loaded, please correct your setup" )
end
def pause_movie_record( layer_range=nil, velocity=nil, val=nil )
puts "pause movie..." if @verbose
puts( "pause: Movimaker not loaded, please correct your setup" ) if !@mm
@mm_record = false
end
def stop_movie_record( layer_range=nil, velocity=nil, val=nil )
puts "stop movie..." if @verbose
puts( "stop: Movimaker not loaded, please correct your setup" ) if !@mm
@mm.finish() if @mm # Finish the movie if space bar is pressed!
@mm_record = false
end
# === Utility, standard-"processing-callback" function[s] ===
def mousePressed(mouse_test=nil) # standard processing "mouse down-callback", used to reload settings-file and for optional debugging here
load( setting = (SETTINGS + ".rb"))
puts "Setting #{setting} loaded"
end
# --- alternative method to stop movie-recording ---
def keyPressed(key_test=nil)
if (key == ' ')
puts "finish movie..."
@mm.finish() # Finish the movie if space bar is pressed!
@mm_record = false
end
end
end # class Oskar
Oskar.new(:title => "Oskar") # Start main-application -> adopt screen resolution, verbose mode and other parameters in settings-file "visualSyntSettings.rb"
# --- Special version of basic Midi-Implementation for JRuby to be used with "VisualSynth"-program with "ruby-processing"---
# --- to be used by your system+DAW (digital audio workstation, ableton live and alike...)
module JavaMidi
midi = javax.sound.midi
import midi.MidiSystem
import midi.MidiDevice
import midi.MidiEvent
import midi.ShortMessage
import midi.Receiver
end
class JRmidiCtrl # Java midi Interface for JRuby (ruby-processing and others)
ROTATE_FACTOR = 360.0 / 16383 # pitch full down is 0 degrees, middle 180, full up 360 degrees - for pitchbend
ROTATE_FACTOR_CONTROLLER = 360.0 / 126 # pitch full down is 0 degrees, middle 180, full up 360 degrees - for controller
@@RPmidiIn = Array.new # class variable to monitor number of initialisations...
def initialize( processing_instance, port_id, params ) # establish new MIDI-in or out-connections to be used in the application
@vSynth = processing_instance # handle to visualSynth-object...
@bottomLayer = @vSynth.bottomLayer # remember on startup, will be updated locally if needed
@ceilingLayer = @vSynth.ceilingLayer # remember on startup, will be updated locally if needed
# Up to 4 in-ports, one is enough - better more for "load-balancing" + special handling of pitchbend on each port, see "help-section" in settings-file...
@port_1, @port_2, @port_3, @port_4 = params[:port_1], params[:port_2], params[:port_3], params[:port_4]
@port_id = port_id
@ctrl = params[:ctrl] # array of 127 controller values on corresponding layer[s]
@idx = params[:idx] # logical number of media on corresponding layer
@allow_note_off = params[:allow_note_off] # note off triggers "black" or not...
@prg = params[:prg] # logical number of media-directory on corresponding layer
@velocity_to_rotate = params[:velocity_to_rotate] # pics will respond to z-rotation with alpha when triggered...
@velocity_to_transparency = params[:velocity_to_transparency] # pics will respond to velocity with alpha when triggered...
@velocity_to_size = params[:velocity_to_size] # pics will respond to velocity with R+G+B when triggered...
@width = params[:width] # "canvas-width"
@height = params[:height] # "canvas-height"
@background = params[:background] # RGB-value for background...
@coord_x = params[:coord_x] # x-position of top/left corner of "canvas" for current layer
@coord_y = params[:coord_y] # y-position of top/left corner of "canvas" for current layer
@coord_x_factor = params[:coord_x_factor] # shift-scalator for x-pos of "canvas" for current layer
@coord_y_factor = params[:coord_y_factor] # shift-scalator for y-pos of "canvas" for current layer
@pos_x = params[:pos_x] # x-position of top/left corner of image / internal use...
@pos_y = params[:pos_y] # y-position of top/left corner of image / internal use...
@base_x = params[:base_x] # x-position of top/left corner of canvas, use to "shift all"
@base_y = params[:base_y] # y-position of top/left corner of canvas, use to "shift all"
@size_x = params[:size_x] # absolute layer width, media will be scaled to fit
@size_y = params[:size_y] # absolute layer height, media will be scaled to fit
@size_x_factor = params[:size_x_factor] # relative layer width - will be scaled to absolute layer width, depending on widht of "canvas"
@size_y_factor = params[:size_y_factor] # relative layer height - will be scaled to absolute layer width, depending on widht of "canvas"
@rotate_x = params[:rotate_x] # rotate across "X-Achis" 0.0 to 720.0 degrees
@rotate_y = params[:rotate_y] # rotate across "Y-Achis" 0.0 to 720.0 degrees
@rotate_z = params[:rotate_z] # rotate across "Z-Achis" => Standard-rotate, 0.0 to 720.0 degrees
@width_factor = 8192.0 / @width # remember scale-factor for pitchbend-logic (performance optim)
@height_factor = 8192.0 / @height # remember scale-factor for pitchbend-logic (performance optim)
@note_names = params[:note_names] # key-names for error-messages...
@midi_in_port = port_id
@echo = params[:echo] # echo key hit and method called afterwards if true...
@verbose = params[:verbose]
puts "MIDI-in port '#{@port_id}' used"
@midiMsg = JavaMidi::ShortMessage.new
if( @@RPmidiIn[port_id] ) # is this instance already inited?
puts "INport: " + @port_id.to_s + " -> double init blocked OK..." if @verbose
@inputDevice = @@RPmidiIn[port_id] # "reload" midi-in handle as remembered be previous instance...
else
in_port = JavaMidi::MidiSystem.getMidiDeviceInfo[@midi_in_port]
puts "INport: " + in_port.to_s
@inputDevice = JavaMidi::MidiSystem.getMidiDevice(in_port)
@inputDevice.open()
transmit = @inputDevice.getTransmitter()
puts transmit.to_s if @verbose
transmit.setReceiver(self)
@@RPmidiIn[port_id] = @inputDevice # remember "handle" associated to the given MIDI-in, in case if instanciated more than once for midi-in-methods later
end
end
# --- set values depending on current controller-value given ---
def layer_values_from_ctrl( layer, ctrl, ctrl_val )
case ctrl
when 1 # Red
@ctrl[layer][1] = ctrl_val << 1
when 2 # Green
@ctrl[layer][2] = ctrl_val << 1
when 3 # Blue
@ctrl[layer][3] = ctrl_val << 1
when 4 # Alpha
@ctrl[layer][4] = ctrl_val << 1
when 5 # x-size, left justified
@size_x[layer] = (ctrl_val*0.01 * @width * @size_x_factor[layer]).floor # max-range: 1.27 * factor (with controllers also max 1.27) * width...
when 6 # y-size, top justified
@size_y[layer] = (ctrl_val*0.01 * @height * @size_y_factor[layer]).floor # max-range: 1.27 * factor (with controllers also max 1.27) * width...
when 7 # x-size, middle justified
@size_x[layer] = x_res = (ctrl_val*0.01 * @width * @size_x_factor[layer]).floor # max-range: 1.27 * factor (with controllers also max 1.27) * width...
@pos_x[layer] = (@width - x_res)/2
when 8 # y-size, middle justified
@size_y[layer] = y_res = (ctrl_val*0.01 * @height * @size_y_factor[layer]).floor # max-range: 1.27 * factor (with controllers also max 1.27) * width...
@pos_y[layer] = (@height - y_res)/2
when 9 # x-size, right justified
@size_x[layer] = x_res = (ctrl_val*0.01 * @width * @size_x_factor[layer]).floor # max-range: 1.27 * factor (with controllers also max 1.27) * width...
@pos_x[layer] = @width - x_res
when 10 # y-size, buttom justified
@size_y[layer] = y_res = (ctrl_val*0.01 * @height * @size_y_factor[layer]).floor # max-range: 1.27 * factor (with controllers also max 1.27) * width...
@pos_y[layer] = @height - y_res
when 11 # x-size-factor
@size_x_factor[layer] = ctrl_val*0.01
when 12 # x-size-factor
@size_y_factor[layer] = ctrl_val*0.01
when 13 # z-rotate centered "canvas"
@rotate_z[layer] = ctrl_val * ROTATE_FACTOR_CONTROLLER
when 14 # x-rotate centered "canvas"
@rotate_x[layer] = ctrl_val * ROTATE_FACTOR_CONTROLLER
when 15 # y-rotate centered "canvas"
@rotate_y[layer] = ctrl_val * ROTATE_FACTOR_CONTROLLER
when 16 # x-Position of "canvas", may use MIDI-default for x/y matrix-controllers here
@coord_x[layer] = (ctrl_val*0.01 * @width * @size_x_factor[layer]).floor
when 17 # y-Position of "canvas", may use MIDI-default for x/y matrix-controllers here
@coord_y[layer] = (ctrl_val*0.01 * @height * @size_y_factor[layer]).floor
when 18 # Background Red
@background[0] = ctrl_val << 1
when 19 # Background Green
@background[1] = ctrl_val << 1
when 20 # Background Blue
@background[2] = ctrl_val << 1
when 123 # caution: controller 123 (val 0) == "all notes off", so do not use directly in your application...
@vSynth.reset_all()
end
end
def send(msg, time_stamp) # "Interface"-method for javax.sound.midi.Receiver, "callback-method" for MIDI-input
layer = (channel=msg.getChannel) >> 1 # External representation in DAW: layer 1: MIDI-channel 1+2, layer 2: MIDI-channel 3+4,...
case msg.getCommand # decide what to do depending on midi-event-type...
when 0x80 # --- note off ---
if( channel%2 == 0 )
@idx[layer]=nil if(@allow_note_off[layer]) # "media channels 1,3,5...13) "fade to black"? - one note higher than MIDI => black..., all done so far: note off processed
else # "modifier channels 2,4,...14, (16))
settings = (channel==14) ? (VisualSynth::global_settings) : (VisualSynth::settings)
if( :set_effect_on_off == settings[msg.getData1][1] ) # switch effect off now?
if( channel==14)
layer_range = (0..7) # set all layers, even if invisible when on global channel...
else
layer_range = (channel==15) ? (@bottomLayer..@ceilingLayer) : (layer..layer) # set all visible layers to value(s) or current one, depending on global mode or not
end
@vSynth.set_effect_on_off( layer_range, false ) # turn effect[s] on current or on all visible layers off
end
end
when 0xb0 # --- ctrl ---
ctrl = msg.getData1
ctrl_val = msg.getData2
if( layer == 7 ) # global [modifier] channel ?
if( ctrl == 0 ) # Bank change: bottom layer to be set for cycling of layers from bottom to top?
@vSynth.bottomLayer = @bottomLayer = ctrl_val%7 # only 8 layers, "fold down" if higher value given, to avoid error-messages...
else
if( ctrl == 32 ) # Bank change: top layer to be set for cycling of layers from bottom to top?
@vSynth.ceilingLayer = @ceilingLayer = ctrl_val%7 # Sub-Bank change: top layer to be set for cycling of layers from bottom to top?
else # controller-change on global-Channel, for instance to be used for Alpha-Fade on all layers...
(@bottomLayer..@ceilingLayer).each { |layer| layer_values_from_ctrl( layer, ctrl, ctrl_val ) }
end
end
else # controller-change on standard-Channel
layer_values_from_ctrl( layer, ctrl, ctrl_val )
end
when 0x90 # --- note on ---
if( channel%2 == 0 && channel != 14 ) # picture channel?
@idx[layer] = msg.getData1 # set_pic(channel, note)
if( @velocity_to_transparency[layer] ) # pics will respond to velocity withalpha-transparency when triggered...
@ctrl[layer][4] = msg.getData2 << 1
end
# pics will respond to velocity with alpha when triggered...
if( @velocity_to_size[layer] ) # pics will respond to velocity with size when triggered...
val = msg.getData2 * 0.01 # 127 becomes 1.27 and so on...
@size_x[layer] = x_res = (val * @width * @size_x_factor[layer]).floor # max-range: 1.27 * factor (with controllers also max 1.27) * width...
@pos_x[layer] = (@width - x_res)/2
@size_y[layer] = y_res = (val * @height * @size_y_factor[layer]).floor # max-range: 1.27 * factor (with controllers also max 1.27) * width...
@pos_y[layer] = (@height - y_res)/2
end
if( @velocity_to_rotate[layer] ) # pics will respond to velocity with z-rotation when triggered...
@rotate_z[layer] = (msg.getData2-1) * ROTATE_FACTOR_CONTROLLER
end
else # media channel !
settings = (channel==14) ? (VisualSynth::global_settings) : (VisualSynth::settings)
if( sym = settings[note=msg.getData1][1] ) # format-example: [0, :layer_on, false]
if( channel==14)
layer_range = (0..7) # set all layers, even if invisible when on global channel...
else
layer_range = (channel==15) ? (@bottomLayer..@ceilingLayer) : (layer..layer) # set all visible layers to value(s) or current one, depending on global mode or not
end
if( sym != :set ) # special setter-routine for "layer-big" array of instance-variables in main-loop...
puts "Layer(%d)/Channel(%d)/%s -> %s: calling method '%s'" % [layer+1, channel+1, @note_names[note], settings[note][0], sym.to_s ] if (@echo[7] && @verbose) # echo notename and channel for tracing-purposes...
@vSynth.send( sym, layer_range, msg.getData2, settings[note][2] )
else # standard setter-routine for "layer-big" array of instance-variables in main-loop...
param=settings[note]
if( param[2].class == Array ) # check if params are 2 individual ones or an array of two or more settings to be done...
paramArray = param[2]
else
paramArray = [ [param[2], param[3]] ]
end
puts "Layer(%d)/Channel(%d)/%s -> %s: calling set '%s'" % [layer+1, channel+1, @note_names[note], settings[note][0], paramArray.inspect ] if (@echo[7] && @verbose) # echo notename and channel for tracing-purposes...
@vSynth.dispatch( layer_range, note, paramArray ) # decide which instance-variables to be set in "main-loop"...
end
else
puts "(#{layer+1}..#{layer+1}) note: #{@note_names[note]} not assigned..." if @verbose
end
end
when 0xe0 # --- pitch-change ---
case @port_id # which MIDI-port is this instance of MIDI-in handler mapped to?
when @port_1 # set absolute x/y pos for active or all layers
val = ((msg.getData2 << 7 ) | msg.getData1)-8192 # 14 bit values 0..16384, x/y origin 0/0 is top left corner!
# caution: 8192 is sent on chanelchange with some DAWs like ableton live, so we have to take care and use this middle-value of poinchange instead of real zero!
if( channel%2 == 0 ) # "media channels 1,3,5...15
res = (val / ( 8192.0 / @width ) * @coord_x_factor[layer]).floor # max-range: 16384.0
if( layer != 7 ) # not global?
@coord_x[layer] = res
else
(@bottomLayer..@ceilingLayer).each {|layer| @coord_x[layer] = res }
end
else # modifier channels 2,4,6...16
res = (val / ( 8192.0 / @height ) * @coord_y_factor[layer]).floor
if( layer != 7 ) # not global?
@coord_y[layer] = res
else
(@bottomLayer..@ceilingLayer).each {|layer| @coord_y[layer] = res }
end
end
when @port_2 # scale + rotate
val = ((msg.getData2 << 7 ) | msg.getData1) # normal: scale modify: image width/height...
if( channel%2 == 0 ) # "media channels 1,3,5...15: scale x/y-width
x_res = ( val/@width_factor*@size_x_factor[layer] ).floor
y_res = ( val/@height_factor*@size_y_factor[layer] ).floor
if( layer != 7 ) # not global?
puts "scale --- internal..."
@size_x[layer] = x_res; @size_y[layer] = y_res;
@pos_x[layer] = (@width - x_res)/2
@pos_y[layer] = (@height - y_res)/2 # keep central
else
(@bottomLayer..@ceilingLayer).each { |layer|
@size_x[layer] = x_res; @size_y[layer] = y_res
@pos_x[layer] = (@width - x_res)/2
@pos_y[layer] = (@height - y_res)/2 # keep central
}
end
else # modifier channels 2,4,6...16: rotate
if( layer != 7 ) # not global?
puts "rotate z --- internal..."
@rotate_z[layer] = val * ROTATE_FACTOR
else
(@bottomLayer..@ceilingLayer).each { |layer| @rotate_z[layer] = val * ROTATE_FACTOR }
end
end
when @port_3 # tilt + flip
val = ((msg.getData2 << 7 ) | msg.getData1) # normal: scale modify: image width/height...
if( channel%2 == 0 ) # "media channels 1,3,5...15: rotate-X
if( layer != 7 ) # not global?
puts "rotate x --- internal..."
@rotate_x[layer] = val * ROTATE_FACTOR
else
(@bottomLayer..@ceilingLayer).each { |layer| @rotate_x[layer] = val * ROTATE_FACTOR }
end
else # modifier channels 2,4,6...16: rotate-Y
if( layer != 7 ) # not global?
puts "rotate y --- internal..."
@rotate_y[layer] = val * ROTATE_FACTOR
else
(@bottomLayer..@ceilingLayer).each { |layer| @rotate_y[layer] = val * ROTATE_FACTOR }
end
end
when @port_4 # alpha-intencity or alpha-crossfade
if( channel%2 != 0 ) # midi-modify-channels 2,4,6.... --- crossfade! --- (only process crossfades on modifier-channels)
val = ((msg.getData2 << 1) | (msg.getData1 >> 6)) # quicker, but a bit inacourate...
if( layer == 7 ) # global crossfade on last layer, logic see below...
layer = @bottomLayer
while( layer < @ceilingLayer ) # crossfade layers 1/2, 3/3... or 2/3, 4/5... depending on bottom- and ceiling-layer!
@ctrl[layer][4] = 256-val # map crossfade onto controller 4..., "this" layer...
@ctrl[layer+1][4] = val # map crossfade onto controller 4..., "next neighbour"
layer += 2
end
else
@ctrl[layer][4] = 256-val # map crossfade onto controller 4..., "this" layer...
@ctrl[layer+1][4] = val # map crossfade onto controller 4..., "next neighbour"
end
else # alpha fade active layer
val = 256-((((msg.getData2 << 2) | (msg.getData1 >> 7)) ) - 256 ).abs # # quicker, but a bit inacourate...: positive values 0..[252]..256 for pitchbend up or down !
# val = (((((msg.getData2 << 7 ) | msg.getData1)-8192) / 32.0 ).floor).abs # positive values 0..256 for pitchbend up or down !
if( layer == 7 ) # global alpha-fade (on MIDI-channel 15)
(@bottomLayer..@ceilingLayer).each {|layer| @ctrl[layer][4] = val }
else
@ctrl[layer][4] = val
end
end
else
puts "pitchbend ignored on port #{@port_id}" if( @verbose )
end
when 0xd0 # --- channel pressure ---, change R G B at once..., color-fade to be combined with alpha-values for different contrast/brithness-effects...
rgb = msg.getData1 << 1
if( layer != 7 ) # not [global] modifier channel ?
@ctrl[layer][1] = rgb; @ctrl[layer][2] = rgb; @ctrl[layer][3] = rgb
else
(@bottomLayer..@ceilingLayer).each {|layer| @ctrl[layer][1] = rgb; @ctrl[layer][2] = rgb; @ctrl[layer][3] = rgb }
end
when 0xc0 # --- program change, only valid for pic-layers, not needed globally yet ---...
if( channel%2 == 0 ) # media-channel
@prg[layer] = msg.getData1 # program_change( msg.getChannel, msg.getData1 ) # single data-byte message!
else
puts "program-change on modifier-channel #{channel+1} -> ignored..." if @verbose
end
else
if( @verbose )
puts "new MIDI-message 0x%x, channel: %d, data1: %d, data2: %d" % [msg.getCommand, msg.getChannel, msg.getData1, msg.getData2]
end
return # we got a MIDI-message here, that we do not want to process...
end
rescue # --- "callback-function" not implemented ? ---
if( @verbose )
puts "MIDI-message 0x%x, channel: %d, data1: %d, data2: %d, not processed..." %
[msg.getCommand, msg.getChannel+1, msg.getData1, msg.getData2]
end
end
def close # "Interface"-method for javax.sound.midi.Receiver, can be called remotely to close the device
@inputDevice.close
end
# === unused: User-functions for MIDI-output, in this context ===
end # class JRmidiCtrl
# === Oskar, a MIDI-driven graphics-animation-application. Documentation: ===
# === MIDI-Channel 1+2: Layer 1 Media (Pics, Movies), Layer 1 Modifier-Params... picture channel/modifier channel and so on ===
# Layer 1: ch 1+2, Layer 2: ch 3+4, Layer 3: ch 5+6, Layer 4: ch 7+8, Layer 5: ch 9+10, Layer 6: ch 11+12, Layer 7: ch 13+14, Global-Layer 8: ch 15+16
# "Layer 0": beneath Layer 1 there is a background which can be colourd as you like (details see below), only visible through transparency or if all layers are "off" (see below)
# -----------------------------------------------------------------------------------------------------------------------------------------------------
# You can adjust your screen resolution and verbose mode (true/false) at @@initial_settings, :initial_settings here
# this will result in: Oskar.new(:width => 1024, :height => 768, :title => "Oskar", :verbose => true) # optional param verbose for explicid messages added, change to false if performance matters!
# === Layers with up to 128 Pics (Movies) from footage =============================================================================
# Footage path is ./pic, folders underneath ./pic/xyz-01, ./pic/xyz-02 can be swicthed be program-change, according to their numeric suffixes
# All pictures of format *.png (with optional alpha) or *.jpg [other formats may depend on platform] are loaded into video-memory on startup
# On media-channels (1,3,5,7,9,11,13) key pressures trigger pictures with numeric suffixes like 01-xyz-picture.png, 02-xyz-picture.jpg...
# You can tell which note-number is which note-key, for instance in the "class VisualSynth", below
# Velocity can be used to optionally influence the transparency and/or the size of the picture (see below)
# === Layers with up to 128 different modifiers on each layer... =========================================================================
# On modifier-channels (2,4,6,8,10,12,14,16) key pressures trigger special funtions on the aquivalent layer of globally when channel 16 is given
# velocity may have or have not a special impact, depending on the type of function (see below)V
# === MIDI-Ports ==================================================================================================================
# Ports have to be given here, else the first MIDI-in-port will be used only
# When you have a look at the in-ports in your DAW-setup, the first Port is number 0 and so on.
# One Port is enough, but if you want to "load-balance" the MIDI-traffic and/or use the optional special Pitch-Bend-Modes (see below), you need up to 4 ports
# port_1 = 0 # your MIDI-input where notes and controllers will be mapped to pictures, movies and so on and pitchbend will be mapped to special functions -
# x/y-pos for instance on port_1 - details explained in the "pitch-bend-section" below
# === MIDI-"Events" used, apart from key-events (notes with optional velocity) =========================================================
# -------------------------------------------------------------------------------------------------------------------------------------------------------------
# MIDI-Bank-Select
# -> only valid on Layer 8 (channel 15 or 16)
# Defines the "bootom" and "ceiling" layer, that is the range of visible layers
# -------------------------------------------------------------------------------------------------------------------------------------------------------------
# MIDI-Program-Change
# -> only valid on Layers 1-7
# Defines the folder currently used for pictures on the equivalent layer (see above)
# -------------------------------------------------------------------------------------------------------------------------------------------------------------
# MIDI-Channel-Pressure (aftertouch)
# -> is applied to the equivalent layer or to all visible layers, when given on layer 8 (channel 15,16)
# Aftertouch: R+G+B, for changing "brightness" without changing opaqueness as with alpha-intensity...
# -------------------------------------------------------------------------------------------------------------------------------------------------------------
# MIDI-Controllers
# -> are applied to the equivalent layer or to all visible layers, when given on layer 8 (channel 15,16)
# Controller 1: Red
# Controller 2: Green
# Controller 3: Blue
# Controller 4: Alpha
# Controller 5: x-size, left justified
# Controller 6: y-size, top justified
# Controller 7: x-size, middle justified
# Controller 8: y-size, middle justified
# Controller 9: x-size, right justified
# Controller 10: y-size, buttom justified
# Controller 11: x-size-factor
# Controller 12: x-size-factor
# Controller 13: z-rotate centered "canvas"
# Controller 14: x-rotate centered "canvas"
# Controller 15: y-rotate centered "canvas"
# Controller 16: x-Position of "canvas", may use MIDI-default for x/y matrix-controllers here
# Controller 17: y-Position of "canvas", may use MIDI-default for x/y matrix-controllers here
# Controller 18: Background Red
# Controller 19: Background Green
# Controller 20: Background Blue
# -------------------------------------------------------------------------------------------------------------------------------------------------------------
# MIDI-Pitchbend
# -> Caution: only value that is PORT-dependend! Is applied to the equivalent layer or to all visible layers, when given on layer 8 (channel 15,16)
# port_1: position x / y (odd/even channels) - example: Layer 2 - channel 3-> pos x / channel 4-> pos y
# port_2: scale / rotate (odd/even channels) - example: Layer 8 - channel 15-> scale / channel 16-> rotate[z] _all_ visible layers...
# port_3: rotate x / rotate y (odd/even channels) - example: Layer 8 - channel 15-> tilt (rotateX) / channel 16-> flip (rotateY) _all_ visible layers...
# port_4: fade alpha / crossfade alpha (odd/even channels) - example: Layer 1 - channel 1-> alpha-fade Layer 1 / channel 2-> crossfade layer 1 to 2
# -> Global crossfades on Layer 8 (channel 15), for instance on layers 1/2, 3/4... or 2/3 or 5/6, 7/8, 9/10... depending on bottom- and ceiling-layer!
# === Key-Strokes on modifier channels (2,4,6,8,10,12,14 - global 15 or 16) =================================================================
# === In the data structures below there are 3 types of settings ========================================================================
#
# -------------------------------------------------------------------------------------------------------------------------------------------------------------
# HINT: If you change values in this file, they will be reloaded when mouse-click on output-screen is encountered !
# use this for "live-programming" of your key-setup when deciding on how values should be set and triggered
# -------------------------------------------------------------------------------------------------------------------------------------------------------------
# @@initial_settings
# -> all entries here are loaded on startup! _and_ when an "reset all" ("panic")-MIDI-message is received.
# This is normally the case when you hit "STOP" on your DAW (digital audio worktation - audio/MIDI-software-"sequenzer")
# First parameter is range of layers to be used on, second is methodname, then values to be set or variable/value for method ":set"
# -> Examples
# [(8..8), :set_initials, ':width => 720, :height => 576, :verbose => true' ], # essential: screen-resolution, these values will be set to defaults (800,600,true) if not given...
# [(8..8), :set_midi_ports, ':port_1 => 0, :port_2 => 1, :port_3 => 2, :port_4 => 3' ) ], # essential: your MIDI-in-ports to be used, will default to in-device 0 if not given, explanations see in "help-section" above...
# [(8..8), :enable_movie_record, { :activate => true, :overwrite => true, :name => "test.mov", :framerate => nil, :codec => MovieMaker::MOTION_JPEG_B, :quality => MovieMaker::MEDIUM' } ], # activate has to be true, if movie shall be recorded...
#
# Hint: set :activate to false if no movie shall be recorded - movie will be overwritten directly if :overwrite is true, so be sure to backup your results here!
# Parameters for MovieMaker:
# type Either ANIMATION, BASE, BMP, CINEPAK, COMPONENT, CMYK, GIF, GRAPHICS, JPEG, MS_VIDEO, MOTION_JPEG_A, MOTION_JPEG_B, RAW, SORENSON, VIDEO, H261, H263, H264
# quality Either WORST, LOW, MEDIUM, HIGH, BEST, LOSSLESS
#
# [(8..8), :set, :@echo, true ], # echo all key-triggers of modifier-channels on "stdout"
# [(8..8), :set_layer_range, (1..2) ], # use layer 1 and 2 initially (internal representation of layers go from 0..6 + 7 for special layer...)
# [(1..2), :set, :@layer_on, true ], # first parameter gives range of layers, may go from 1..7 to set all 7 layers...
#
# -------------------------------------------------------------------------------------------------------------------------------------------------------------
# @@global_settings
# -> entries are triggered by equivalent key-values ("notes") on channel 15, only!
# functions here are applied to all layers (visible or not)
# caution: this is only true for modifier-note-events, "global MIDI-controllers" act the same on channel 15 or 16 only to all _visible_ layers
# you may put also functions here which change global settings, for instance setting the background colour
# -> Example: ["009_A-2 ", :set_rgb_background, [255,255,255] ], # sets the background (underneath Layer 1) to white
# ["000_C-2 ", :start_movie_record], # starts movie-record:
# ["000_C-2 ", :pause_movie_record], # pauses movie-record
# ["000_C-2 ", :stop_movie_record], # stops movie-record and "closes" the movie file to be used elsewhere later!
# -------------------------------------------------------------------------------------------------------------------------------------------------------------
# @@settings
# -> entries are triggered by equivalent key-values ("notes") on channel 2,4,6,8,10,12,14 for Layers 1-7 or globally with keypresses on channel 16
# Keypressure for "global use" on channel 16 are applied to all currently visible (!) layers, only.
# -> Settings mentioned here can also be applied as "initial_settings" (range of layers as first parameter here!) or as "global_settings"
#
# ["000_C-2 ", :set_rgb, [254,80,127]], # switch layer on or off...,
# ["000_C-2 ", :set_alpha, 64], # switch layer on or off...,
# ["000_C-2 ", :set_base_coords, [0,0]] # top left corner of screen, reletive for "canvas"
# ["000_C-2 ", set_canvas_size, [0.5, 0.5]], # relative size of "canvas" (image may be shrinked or largende "inside")...
# ["000_C-2 ", :set_rgb_background, [60,70,89]], # background-color - set be corresponding note...
#
# ["000_C-2 ", :set_r_from_velo], # transform velo to R,G,B or A...
# ["000_C-2 ", :set_g_from_velo], # transform velo to R,G,B or A...
# ["000_C-2 ", :set_b_from_velo], # transform velo to R,G,B or A...
# ["000_C-2 ", :set_a_from_velo], # transform velo to R,G,B or A...
# ["000_C-2 ", :set_r_bg_from_velo], # transform velo to R,G,B of background
# ["000_C-2 ", :set_g_bg_from_velo], # transform velo to R,G,B of background
# ["000_C-2 ", :set_b_bg_from_velo], # transform velo to R,G,B of background
# ["000_C-2 ", :set_pic_idx_from_velo], # switch picture from velocity
# ["000_C-2 ", :set_rotate_x_from_velo], # rotate-x from velocity
# ["000_C-2 ", :set_rotate_y_from_velo], # rotate-y from velocity
# ["000_C-2 ", :set_rotate_z_from_velo], # rotate-z from velocity
# ["000_C-2 ", :set_canvas_scale_from_velo], # transform velo to (centered) x+y-size of "canvas"
# ["000_C-2 ", :set_canvas_x_size_from_velo], # transform velo to (centered) x-size of "canvas"
# ["000_C-2 ", :set_canvas_y_size_from_velo], # transform velo to (centered) y-size of "canvas"
# ["000_C-2 ", :set_x_coord_from_velo], # transform velo to x-pos of "canvas"
# ["000_C-2 ", :set_y_coord_from_velo], # transform velo to y-pos of "canvas"
#
# ["000_C-2 ", :set_layer_range, (1..2) ], # same as bank-chage on global layer: set bottom and ceiling of layers to show, val is given as range here
# ["000_C-2 ", :reset_effects], # may be called by default-method or without params internally to reset (initalize _and_ switch of _all_ effect blend+filter)
# ["000_C-2 ", :set_blend, [0, 0, 1, 1, 0, 0, 1, 1, Oskar::ADD] ], # example of effect_params: [0, 0, width, height, 0, 0, width, height, 2] -> blendmode ADD for entire image
# ["000_C-2 ", :set_blend, nil ], # set blend-effect off for given layer[s], details on different possible blend-modes see below
# ["000_C-2 ", :set_filter, Oskar:: GRAY ], # example of effect_params, details on different filter-modes see below
# ["000_C-2 ", :set_filter, nil ], # set filter-effect off for given layer[s]
# ["000_C-2 ", :set_effect_on_off], # provide current 'effect on' at note-off / 'effect off' at note-off...
# ["000_C-2 ", :reset_all], # "panic-button" ;-)
#
# -------------------------------------------------------------------------------------------------------------------------------------------------------------
# -> :set Setter-function for several control-variables...
#
# ["000_C-2 ", :set, :@echo, false ], # echo keys and their function pressed on modifier-layers or not...
# ["000_C-2 ", :set, :@base_x, 0 ], # x-position of canvas relative top/left corner...,
# ["000_C-2 ", :set, :@base_y, 0], # y-position of canvas relative top/left corner...,
#
# -> use more than one setter at once, if you like: ["000_C-2 ", :set, [[ :@base_x, 0.2],[:@base_y, 0.5]] ],
# -> all positions and sizes are given as percent, relative to width/height of window or "canvas"
#
# ["000_C-2 ", :set, :@velocity_to_transparency, true], # pics will respond to velocity with alpha when triggered / switch off by false...
# ["000_C-2 ", :set, :@velocity_to_size, true], # pics will respond to velocity with size when triggered / switch off by false...
# ["000_C-2 ", :set, :@velocity_to_rotate, true], # pics will respond to velocity with z-rotation when triggered / switch off by false...
# ["000_C-2 ", :set, :@allow_note_off, true], # note off triggers "black" (background/pic beneath) or not / switch off by false...
# ["000_C-2 ", :set, :@geometry_on, true] # allow rotate x/y/z or not / switch off by false...
# ["000_C-2 ", :set, :@layer_on, true], # switch layers on/off set Layer 1 and 2 on in global setting for instance / switch off by false...
# ["000_C-2 ", :set, :@effect_on, true], # apply effect, if any (blend and/or filter) / switch off by false...
# ["000_C-2 ", :set, :@filter_before_blend, true] # fiter, then blend if true or blend, then filter if false... (if effects are on)
#
# ["000_C-2 ", :set, :@coord_x, 0.5], # x-position of image relative top/left corner of "canvas" for current layer
# ["000_C-2 ", :set, :@coord_y, 0.5], # x-position of image relative top/left corner of "canvas" for current layer
# ["000_C-2 ", :set, :@coord_x_factor, 1.0], # scale "canvas-width" of current layer to 100 %
# ["000_C-2 ", :set, :@coord_y_factor, 1.0], # scale "canvas-height" of current layer to 100 %
#
# ["000_C-2 ", :set, :@size_x_factor, 1.0], # scale "image-width" of current layer to 100 %
# ["000_C-2 ", :set, :@size_y_factor, 1.0], # scale "image-height" of current layer to 100 %
#
# ["000_C-2 ", :set, :@rotate_x, 0.0], # rotate (flip): value in degrees 0-360 (or even bigger or negative)...
# ["000_C-2 ", :set, :@rotate_y, 180], # rotate (tilt): in degrees 0-360 (or even bigger or negative)...
# ["000_C-2 ", :set, :@rotate_z, 360] # rotate: value in degrees 0-360 (or even bigger or negative)...
# ["000_C-2 ", :set, :@prg, 0] # folder-number (normally set be program-change!) HINT: not automatically reset on "reset all" - set within @@initial_settings to do so...
# ["000_C-2 ", :set, :@pic_idx , 0] # picture-index (normally set be note on media-channel!) HINT: not automatically reset on "reset all" - set within @@initial_settings to do so...
#
# HINT: to set a single control-variable (not an array of different ones at once, which is possible here, too!)
# an alternative notation with any prefix of type "xyz_" like "set_" or "changeVal_" is available:
# EXAMPLE: [(2..2), :change_size_x_factor, 1.0 ], # alternative notation for: [(2..2), :set, :@size_x_factor, 1.0 ],
# this is better readable, but may be a bit slower...
#
# -------------------------------------------------------------------------------------------------------------------------------------------------------------
# === processing blend modes (http://www.processing.org/reference/blend_.html), java-example: blend(img, 0, 0, 33, 100, 67, 0, 33, 100, ADD); ===
# --- caution: some blend-modes are slow! --- (add Processing::App:: to all modes, see below )
# Oskar::BLEND - linear interpolation of colours: C = A*factor + B
# ...ADD - additive blending with white clip: C = min(A*factor + B, 255)
# SUBTRACT - subtractive blending with black clip: C = max(B - A*factor, 0)
# DARKEST - only the darkest colour succeeds: C = min(A*factor, B)
# LIGHTEST - only the lightest colour succeeds: C = max(A*factor, B)
# DIFFERENCE - subtract colors from underlying image.
# EXCLUSION - similar to DIFFERENCE, but less extreme.
# MULTIPLY - Multiply the colors, result will always be darker.
# SCREEN - Opposite multiply, uses inverse values of the colors.
# OVERLAY - A mix of MULTIPLY and SCREEN. Multiplies dark values, and screens light values.
# HARD_LIGHT - SCREEN when greater than 50% gray, MULTIPLY when lower.
# SOFT_LIGHT - Mix of DARKEST and LIGHTEST. Works like OVERLAY, but not as harsh.
# DODGE - Lightens light tones and increases contrast, ignores darks. Called "Color Dodge" in Illustrator and Photoshop.
# BURN - Darker areas are applied, increasing contrast, ignores lights. Called "Color Burn" in Illustrator and Photoshop.
# All modes use the alpha information (highest byte) of source image pixels as the blending factor.
# If the source and destination regions are different sizes, the image will be automatically resized to match the destination size. If the srcImg parameter is not used, the display window is used as the source image.
# === filter (http://www.processing.org/reference/filter_.html), java-example: filter(BLUR, 6); ===
# --- caution: filters are _very_ slow --- (add Processing::App:: to all modes, see below )
# Oskar::THRESHOLD - converts the image to black and white pixels depending if they are above or below the threshold defined by the level parameter. The level must be between 0.0 (black) and 1.0(white). If no level is specified, 0.5 is used.
# ...INVERT - sets each pixel to its inverse value
# POSTERIZE - limits each channel of the image to the number of colors specified as the level parameter
# BLUR - executes a Guassian blur with the level parameter specifying the extent of the blurring. If no level parameter is used, the blur is equivalent to Guassian blur of radius 1.
# GRAY - converts any colors in the image to grayscale equivalents
# OPAQUE - sets the alpha channel to entirely opaque.
# ERODE - reduces the light areas with the amount defined by the level parameter.
# DILATE - increases the light areas with the amount defined by the level parameter.
class VisualSynth
# === initial_settings are "function-calls" that are called only once at setup ===
@@initial_settings = # so add your functions for your current setup here...
[ # -> no mapping keys or anything, just to provide identical syntax as settings and globals
# --- These first 3 option should be adapted, otherwise defaults will be taken ---
# [(8..8), :set_initials, ':width => 1024, :height => 768, :framerate => 25, :verbose => true' ], # these values will be set to defaults (800,600,25,true) if not given...
[(8..8), :set_initials, ':width => 1024, :height => 768, :framerate => 25, :verbose => true' ], # these values will be set to defaults (800,600,25,true) if not given...
[(8..8), :set_midi_ports, ':port_1 => 0, :port_2 => 1, :port_3 => 2, :port_4 => 3' ], # your MIDI-in-ports to be used, explanations see in "help-section" above...
[(8..8), :enable_movie_record, ':activate => false, :overwrite => true, :name => "oskar.mov", :codec => MovieMaker::MOTION_JPEG_B, :quality => MovieMaker::MEDIUM' ], # activate has to be true, if movie shall be recorded...
# ":framerate => xyz" will be as in animation if [nil or] not set, for example: @mm = MovieMaker.new(self, width, height, "oskar.mov", 25, MovieMaker::MOTION_JPEG_B, MovieMaker::MEDIUM)
# --- optional values start here ---
[(8..8), :set_layer_range, (1..3) ], # , use (external representation) layer 1 and 2 initially (internal representation of layers go from 0..6 + 7 for special layer...)
# [(1..3), :set, :@layer_on, true ], # first parameter gives range of layers, may go from 1..7 to set all 7 layers...(internal representation of layers go from 0..6 + 7 for special layer...)
[(8..8), :set, :@echo, false ], # echo all key-triggers of modifier-channels on "stdout"
[(2..2), :set_size_x_factor, 1.0 ], # alternative notation for: [(2..2), :set, :@size_x_factor, 1.0 ],
[(2..2), :set, :@size_y_factor, 1.0 ],
[(1..1), :set_canvas_size, [2.5, 2.5] ],
[(2..2), :set, :@velocity_to_transparency, false ], # pics will respond to velocity withalpha-transparency when triggered...
[(2..2), :set, :@velocity_to_size, false ], # pics will respond to velocity with size when triggered...
[(1..8), :set_base_coords, [0.0, 0.0] ], # top-left-corner for "canvas-position" on each layer, normally goes from 0.0 to 1.0 (1.0 on x ==width 1.0 on y==height), but may be negativ or bigger for special cases
[(1..8), :set, :@geometry_on, true ], # allow rotate, tilt and flip...
]
# === "macros", to be enabled on channels 2,4,6,8,10,13,14 for layers 1,2,3,4,5,6,7 or on channel 16 ("Modifier Channel" of Layer 8) for layers 1..7! , channel 15 see below for "globals" ===
@@settings = # -> edit key to function mapping and function-parameters as you like...
[ # CAUTION: do not delete any lines or change position of lines (for speed purposes array-index is used)
["000_C-2 ", :set, :@layer_on, false ],
["001_C#-2", :set, :@layer_on, true ],
["002_D-2 ", :set, :@geometry_on, false ],
["003_D#-2", :set, :@geometry_on, true ],
["004_E-2 ", :set, :@allow_note_off, false ],
["005_F-2 ", :set, :@allow_note_off, true ],
["006_F#-2", :assign_velocity_to_rotate, true ],
["007_G-2 ", :set, [ [:@layer_on, true],[:@allow_note_off, true]] ],
["008_G#-2", :assign_velocity_to_transparency, true ],
["009_A-2 ", :set_rgb_background, [255,0,0] ],
["010_A#-2", nil, [] ],
["011_H-2 ", :set_rgb_background, [0,0,0] ],
["012_C-1 ", :set_rgb, [30,80,254] ],
["013_C#-1", :set_rgb, [254,254,254] ],
["014_D-1 ", :set_rgb, [254,80,127] ],
["015_D#-1", nil, [] ],
["016_E-1 ", :set_blend, [0, 0, 1, 1, 0, 0, 1, 1, Oskar::ADD] ], # 2: ADD
["017_F-1 ", :set_blend, nil ], # blend off
["018_F#-1", :set_effect_on_off ],
["019_G-1 ", :set_filter, 12 ], # GREY...
["020_G#-1", nil, [] ],
["021_A-1 ", :reset_effects, nil ], # filter off
["022_A#-1", nil, [] ],
["023_H-1 ", :set_filter, nil ], # filter off
["024_C0 ", :set_alpha, 127 ],
["025_C#0 ", nil, [] ],
["026_D0 ", :set_alpha, 255 ],
["027_D#0 ", :reset_all, 255 ], # "all notes off", set geometry+effects off, set RGBA to "high-value"...
["028_E0 ", :set, :@filter_before_blend, true ],
["029_F0 ", :set, :@filter_before_blend, false ],
["030_F#0 ", nil, [] ],
["031_G0 ", :set_base_coords, [0, 0]] ,
["032_G#0 ", nil, [] ],
["033_A0 ", :set_base_coords, [-0.2, 0.2]] ,
["034_A#0 ", nil, [] ],
["035_H0 ", nil, [] ],
["036_C1 ", :set, :@size_x_factor, 2.5 ],
["037_C#1 ", nil, [] ],
["038_D1 ", :set, :@size_y_factor, 3.0 ],
["039_D#1 ", nil, [] ],
["040_E1 ", :set, :@size_x_factor, 1.0 ],
["041_F1 ", :set, :@size_x_factor, 0.5 ],
["042_F#1 ", nil, [] ],
["043_G1 ", :set_r_from_velo ] ,
["044_G#1 ", nil, [] ],
["045_A1 ", :set_g_from_velo ],
["046_A#1 ", nil, [] ],
["047_H1 ", :set_b_from_velo ],
["048_C2 ", :set_a_from_velo ],
["049_C#2 ", nil, [] ],
["050_D2 ", :set_canvas_size_from_velo],
["051_D#2 ", nil, [] ],
["052_E2 ", :set_x_coord_from_velo ],
["053_F2 ", :set_y_coord_from_velo ],
["054_F#2 ", nil, [] ],
["055_G2 ", :set_r_bg_from_velo ],
["056_G#2 ", nil, [] ],
["057_A2 ", :set_g_bg_from_velo ],
["058_A#2 ", nil, [] ],
["059_H2 ", :set_b_bg_from_velo ],
["060_C3 ", :set_canvas_size, [0.5, 0.5]],
["061_C#3 ", nil, [] ],
["062_D3 ", :set_canvas_size, [1.0, 1.0]],
["063_D#3 ", nil, [] ],
["064_E3 ", :set_canvas_scale_from_velo ],
["065_F3 ", :set_canvas_x_size_from_velo ],
["066_F#3 ", nil, [] ],
["067_G3 ", :set_canvas_y_size_from_velo ],
["068_G#3 ", nil, [] ],
["069_A3 ", :set_rotate_z_from_velo ],
["070_A#3 ", :set_rotate_x_from_velo ],
["071_H3 ", :set_rotate_y_from_velo ],
["072_C4 ", nil, [] ],
["073_C#4 ", nil, [] ],
["074_D4 ", nil, [] ],
["075_D#4 ", nil, [] ],
["076_E4 ", nil, [] ],
["077_F4 ", nil, [] ],
["078_F#4 ", nil, [] ],
["079_G4 ", nil, [] ],
["080_G#4 ", nil, [] ],
["081_A4 ", nil, [] ],
["082_A#4 ", nil, [] ],
["083_H4 ", nil, [] ],
["084_C5 ", nil, [] ],
["085_C#5 ", nil, [] ],
["086_D5 ", nil, [] ],
["087_D#5 ", nil, [] ],
["088_E5 ", nil, [] ],
["089_F5 ", nil, [] ],
["090_F#5 ", nil, [] ],
["091_G5 ", nil, [] ],
["092_G#5 ", nil, [] ],
["093_A5 ", nil, [] ],
["094_A#5 ", nil, [] ],
["095_H5 ", nil, [] ],
["096_C6 ", nil, [] ],
["097_C#6 ", nil, [] ],
["098_D6 ", nil, [] ],
["099_D#6 ", nil, [] ],
["100_E6 ", nil, [] ],
["101_F6 ", nil, [] ],
["102_F#6 ", nil, [] ],
["103_G6 ", nil, [] ],
["104_G#6 ", nil, [] ],
["105_A6 ", nil, [] ],
["106_A#6 ", nil, [] ],
["107_H6 ", nil, [] ],
["108_C7 ", nil, [] ],
["109_C#7 ", nil, [] ],
["110_D7 ", nil, [] ],
["111_D#7 ", nil, [] ],
["112_E7 ", nil, [] ],
["113_F7 ", nil, [] ],
["114_F#7 ", nil, [] ],
["115_G7 ", nil, [] ],
["116_G#7 ", nil, [] ],
["117_A7 ", nil, [] ],
["118_A#7 ", nil, [] ],
["119_H7 ", nil, [] ],
["120_C8 ", nil, [] ],
["121_C#8 ", nil, [] ],
["122_D8 ", nil, [] ],
["123_D#8 ", nil, [] ],
["124_E8 ", nil, [] ],
["125_F8 ", nil, [] ],
["126_F#8 ", nil, [] ],
["127_G8 ", nil, [] ]
]
# === "Globals" must be triggered on Channel 15 ("Picture-Channel" of Layer 8), the act on _all_ layers, regardless which layers are visible, ===
@@global_settings = # or shall trigger things like record video - in later versions, maybe...
[ # -> edit key to function mapping and function-parameters as you like...
["000_C-2 ", :start_movie_record ], # CAUTION: do not delete any lines (except at the end, maybe) or change position of lines (for speed purposes array-index is used)
["001_C#-2", nil, [] ],
["002_D-2 ", :stop_movie_record ],
["003_D#-2", nil, [] ],
["004_E-2 ", nil, [] ],
["005_F-2 ", nil, [] ],
["006_F#-2", nil, [] ],
["007_G-2 ", nil, [] ],
["007_G-2 ", :set, [ [:@layer_on, true],[:@allow_note_off, true]] ], # array of different things to set may be given to set()-function...
["009_A-2 ", :set_rgb_background, [255,0,0] ],
["010_A#-2", nil, [] ],
["011_H-2 ", nil, [] ], # 680093
["012_C-1 ", :set_rgb_background, [0xD8, 0x00, 0x20] ], # do, red (colours in solresol notation: http://en.wikipedia.org/wiki/Solresol, temarated in relation to the "plasma-cloud"-picture used here...)
["013_C#-1", nil, [] ],
["014_D-1 ", :set_rgb_background, [0xF7, 0x00, 0x03] ], # re, orange
["015_D#-1", nil, [] ],
["016_E-1 ", :set_rgb_background, [0xE0, 0xE0, 0x15] ], # mi, yello
["017_F-1 ", :set_rgb_background, [0x18, 0xE3, 0x00] ], # fa, green
["018_F#-1", nil, [] ],
["019_G-1 ", :set_rgb_background, [0x00, 0x7E, 0x7B] ], # sol, light blue
["020_G#-1", nil, [] ],
["021_A-1 ", :set_rgb_background, [0x00, 0x30, 0xCC] ], # la, blue
["022_A#-1", nil, [] ],
["023_H-1 ", :set_rgb_background, [0x68, 0x00, 0x93] ], # si, purple
["024_C0 ", nil, [] ],
["025_C#0 ", nil, [] ],
["026_D0 ", nil, [] ],
["027_D#0 ", nil, [] ],
["028_E0 ", nil, [] ],
["029_F0 ", nil, [] ],
["030_F#0 ", nil, [] ],
["031_G0 ", nil, [] ],
["032_G#0 ", nil, [] ],
["033_A0 ", nil, [] ],
["034_A#0 ", nil, [] ],
["035_H0 ", nil, [] ],
["036_C1 ", nil, [] ],
["037_C#1 ", nil, [] ],
["038_D1 ", nil, [] ],
["039_D#1 ", nil, [] ],
["040_E1 ", nil, [] ],
["041_F1 ", nil, [] ],
["042_F#1 ", nil, [] ],
["043_G1 ", nil, [] ],
["044_G#1 ", nil, [] ],
["045_A1 ", nil, [] ],
["046_A#1 ", nil, [] ],
["047_H1 ", nil, [] ],
["048_C2 ", nil, [] ],
["049_C#2 ", nil, [] ],
["050_D2 ", nil, [] ],
["051_D#2 ", nil, [] ],
["052_E2 ", nil, [] ],
["053_F2 ", nil, [] ],
["054_F#2 ", nil, [] ],
["055_G2 ", nil, [] ],
["056_G#2 ", nil, [] ],
["057_A2 ", nil, [] ],
["058_A#2 ", nil, [] ],
["059_H2 ", nil, [] ],
["060_C3 ", nil, [] ],
["061_C#3 ", nil, [] ],
["062_D3 ", nil, [] ],
["063_D#3 ", nil, [] ],
["064_E3 ", nil, [] ],
["065_F3 ", nil, [] ],
["066_F#3 ", nil, [] ],
["067_G3 ", nil, [] ],
["068_G#3 ", nil, [] ],
["069_A3 ", nil, [] ],
["070_A#3 ", nil, [] ],
["071_H3 ", nil, [] ],
["072_C4 ", nil, [] ],
["073_C#4 ", nil, [] ],
["074_D4 ", nil, [] ],
["075_D#4 ", nil, [] ],
["076_E4 ", nil, [] ],
["077_F4 ", nil, [] ],
["078_F#4 ", nil, [] ],
["079_G4 ", nil, [] ],
["080_G#4 ", nil, [] ],
["081_A4 ", nil, [] ],
["082_A#4 ", nil, [] ],
["083_H4 ", nil, [] ],
["084_C5 ", nil, [] ],
["085_C#5 ", nil, [] ],
["086_D5 ", nil, [] ],
["087_D#5 ", nil, [] ],
["088_E5 ", nil, [] ],
["089_F5 ", nil, [] ],
["090_F#5 ", nil, [] ],
["091_G5 ", nil, [] ],
["092_G#5 ", nil, [] ],
["093_A5 ", nil, [] ],
["094_A#5 ", nil, [] ],
["095_H5 ", nil, [] ],
["096_C6 ", nil, [] ],
["097_C#6 ", nil, [] ],
["098_D6 ", nil, [] ],
["099_D#6 ", nil, [] ],
["100_E6 ", nil, [] ],
["101_F6 ", nil, [] ],
["102_F#6 ", nil, [] ],
["103_G6 ", nil, [] ],
["104_G#6 ", nil, [] ],
["105_A6 ", nil, [] ],
["106_A#6 ", nil, [] ],
["107_H6 ", nil, [] ],
["108_C7 ", nil, [] ],
["109_C#7 ", nil, [] ],
["110_D7 ", nil, [] ],
["111_D#7 ", nil, [] ],
["112_E7 ", nil, [] ],
["113_F7 ", nil, [] ],
["114_F#7 ", nil, [] ],
["115_G7 ", nil, [] ],
["116_G#7 ", nil, [] ],
["117_A7 ", nil, [] ],
["118_A#7 ", nil, [] ],
["119_H7 ", nil, [] ],
["120_C8 ", nil, [] ],
["121_C#8 ", nil, [] ],
["122_D8 ", nil, [] ],
["123_D#8 ", nil, [] ],
["124_E8 ", nil, [] ],
["125_F8 ", nil, [] ],
["126_F#8 ", nil, [] ],
["127_G8 ", nil, [] ]
]
# --- calling interface to retreive data from the different setting arrays, used by application, do not change! ---
def self.global_settings
@@global_settings
end
def self.settings
@@settings
end
def self.initial_settings
@@initial_settings
end
end # class VisualSynth
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment