From 921e3afc97f4cc8524e765074968522291e271f2 Mon Sep 17 00:00:00 2001 From: yann marquet Date: Sun, 31 Dec 2017 16:29:20 +0000 Subject: [PATCH 01/11] fire ruby callback upon C callback --- ext/rcoremidi/client.c | 44 ++++++++++++++++--------------------- lib/rcoremidi/client.rb | 7 +++--- lib/rcoremidi/instrument.rb | 4 ++-- lib/rcoremidi/live.rb | 4 ++-- lib/rcoremidi/track.rb | 23 +++++++++---------- 5 files changed, 39 insertions(+), 43 deletions(-) diff --git a/ext/rcoremidi/client.c b/ext/rcoremidi/client.c index f0b2edb..5954447 100644 --- a/ext/rcoremidi/client.c +++ b/ext/rcoremidi/client.c @@ -67,7 +67,7 @@ static VALUE boot_callback_event_thread(void * data) { clientNode->rb_client_obj, rb_intern("on_tick"), 1, - UINT2NUM(clientNode->transport->bar) + UINT2NUM(clientNode->transport->tick_count) ); /* printf ("TRANSPORT: %d \n", clientNode->transport->tick_count); */ pthread_mutex_unlock(&waiting.callback->mutex); @@ -119,6 +119,7 @@ static RCoreMidiTransport * reset_transport(RCoreMidiTransport * transport) { transport->quarter = 0; transport->eigth = 0; transport->sixteinth = 0; + transport->state = kMIDIStop; return transport; } @@ -190,46 +191,39 @@ static void MidiReadProc(const MIDIPacketList *pktlist, void *refCon, void *conn for (i = 0; i < packet->length; ++i) { + /* printf("packet : %04x\n", packet->data[i]); */ switch(packet->data[i]) { case kMIDIStart: - /* static MIDITimeStamp timestamp = 0; */ + printf("kMIDIStart\n"); + transport->state = kMIDIStart; + transport->tick_count++; transport->current_timestamp = mach_absolute_time(); - clientNode->callback->data = (void *)clientNode; + /* clientNode->callback->data = (void *)clientNode; */ - g_callback_queue_push(clientNode->callback); + /* g_callback_queue_push(clientNode->callback); */ break; case kMIDIStop: printf("Stoping Client...\n"); + transport->state = kMIDIStop; break; case kMIDITick: transport->tick_count++; - printf("%d\n", transport->tick_count); - if ((transport->tick_count % 96) == 0) { - clientNode->callback->data = (void *)clientNode; - - g_callback_queue_push(clientNode->callback); - - transport->bar++; - } - // quarter - if ((transport->tick_count % 24) == 0) { - transport->quarter++; - } - // eigth - if ((transport->tick_count % 12) == 0) { - transport->eigth++; - } - // sixteinth - if ((transport->tick_count % 8) == 0) { - transport->sixteinth++; - } + /* printf("%d\n", transport->tick_count); */ + clientNode->callback->data = (void *)clientNode; + g_callback_queue_push(clientNode->callback); break; case kMIDISongPositionPointer: - reset_transport(transport); + printf("kMIDISongPositionPointer\n"); + + if (transport->state == kMIDIStop) { + transport->tick_count++; + } else { + reset_transport(transport); + } break; } diff --git a/lib/rcoremidi/client.rb b/lib/rcoremidi/client.rb index b4c2172..c2bb06a 100644 --- a/lib/rcoremidi/client.rb +++ b/lib/rcoremidi/client.rb @@ -3,9 +3,10 @@ class Client attr_accessor :midi_in, :midi_out - def on_tick(bar) - to_send = live.generate_beats(bar).flatten.compact - send_packets(midi_out, to_send) + def on_tick(current_tick) + # puts current_tick + to_send = live.generate_beats(current_tick).flatten.compact + # send_packets(midi_out, to_send) end def create_live diff --git a/lib/rcoremidi/instrument.rb b/lib/rcoremidi/instrument.rb index 83a0731..1e29aee 100644 --- a/lib/rcoremidi/instrument.rb +++ b/lib/rcoremidi/instrument.rb @@ -13,8 +13,8 @@ def initialize(name, channel, &block) self.block = block end - def generate_bar(bar) - track.generate(bar) + def generate_beat(current_tick) + track.generate(current_tick) end def log(msg) RCoreMidi::Application.config.logger.info msg diff --git a/lib/rcoremidi/live.rb b/lib/rcoremidi/live.rb index cd2cae3..72b158f 100644 --- a/lib/rcoremidi/live.rb +++ b/lib/rcoremidi/live.rb @@ -5,9 +5,9 @@ module RCoreMidi class Live attr_reader :instruments - def generate_beats(bar) + def generate_beats(current_tick) Instrument.all.flat_map do |instrument| - instrument.generate_bar(bar) + instrument.generate_beat(current_tick) end end diff --git a/lib/rcoremidi/track.rb b/lib/rcoremidi/track.rb index ef19c13..1e74d87 100644 --- a/lib/rcoremidi/track.rb +++ b/lib/rcoremidi/track.rb @@ -11,17 +11,18 @@ def initialize(channel) self.channel = channel end - def generate(bar) - if bar == reset_at - return @clips = {} && [] - end - - clip, enable_probability = bars[bar] - - return [] unless clip - clip.rythm_sequences.map do |rythm_sequence| - rythm_sequence.generate(enable_probability, channel).compact - end.inject(:+) + def generate(current_tick) + puts current_tick + # if current_tick == reset_at + # return @clips = {} && [] + # end + + # clip, enable_probability = bars[bar] + + # return [] unless clip + # clip.rythm_sequences.map do |rythm_sequence| + # rythm_sequence.generate(enable_probability, channel).compact + # end.inject(:+) end def play(bar, clip, enable_probability) From 22af32ea615c42938ce0d48e2c286c721cf25fcd Mon Sep 17 00:00:00 2001 From: Yann Marquet Date: Sun, 25 Mar 2018 17:31:34 +0200 Subject: [PATCH 02/11] rubocop goodness --- lib/rcoremidi/daemonize.rb | 62 ++++++++++++++++++-------------------- 1 file changed, 30 insertions(+), 32 deletions(-) diff --git a/lib/rcoremidi/daemonize.rb b/lib/rcoremidi/daemonize.rb index 8a5e0ef..39a60fd 100644 --- a/lib/rcoremidi/daemonize.rb +++ b/lib/rcoremidi/daemonize.rb @@ -1,52 +1,45 @@ -module Rcoremidi - module Daemonize +# frozen_string_literal: true +module Rcoremidi + module Daemonize # :nodoc: def daemonize exit if fork Process.setsid exit if fork yield set_process_title - Dir.chdir "/" + Dir.chdir '/' end def write_pid - unless root.pid_file.exist? - begin - root.pid_file.expand_path.open(::File::CREAT | ::File::EXCL | ::File::WRONLY){|f| f.write("#{Process.pid}") } - at_exit do - if root.pid_file.expand_path.exist? - root.pid_file.expand_path.delete - end - end - rescue Errno::EEXIST - check_pid - retry - end - end + return if root.pid_file.exist? + root.pid_file.expand_path.open(::File::CREAT | ::File::EXCL | ::File::WRONLY) { |f| f.write(Process.pid.to_s) } + at_exit { destroy_pid_file } + rescue Errno::EEXIST + check_pid + retry end def redirect_output FileUtils.touch root.log_file - root.log_file.chmod(0644) + root.log_file.chmod(0o644) $stderr.reopen(root.log_file, 'a') $stdout.reopen($stderr) $stdout.sync = $stderr.sync = true end def set_process_title - Process.setproctitle('arcx live:%d - in %s' % [config.bpm, root]) + Process.setproctitle(format('arcx live:%d - in %s', config.bpm, root)) end def check_pid - if root.pid_file.exist? - case pid_status - when :running, :not_owned - config.logger.info "A server is already running. Check #{root.pid_file}" - exit(1) - when :dead - root.pid_file.delete - end + return unless root.pid_file.exist? + case pid_status + when :running, :not_owned + config.logger.info "A server is already running. Check #{root.pid_file}" + exit(1) + when :dead + root.pid_file.delete end end @@ -54,7 +47,7 @@ def pid_status return :exited unless root.pid_file.exist? pid = root.pid_file.read.to_i return :dead if pid == 0 - Process.kill(0, pid) # check process status + Process.kill(0, pid) # check process status :running rescue Errno::ESRCH :dead @@ -63,11 +56,16 @@ def pid_status end def trap_signals - trap 'QUIT', Proc.new { - listeners.stop - client.dispose - exit(0) - } + trap('QUIT', proc { + listeners.stop + client.dispose + exit(0) + }) + end + + def destroy_pid_file + return unless root.pid_file.expand_path.exist? + root.pid_file.expand_path.delete end end end From 4436354fb4a3f7b94063c1cdc61cfda269adba3c Mon Sep 17 00:00:00 2001 From: Yann Marquet Date: Mon, 26 Mar 2018 08:40:52 +0200 Subject: [PATCH 03/11] remove redundant malloc --- ext/rcoremidi/client.c | 1 - 1 file changed, 1 deletion(-) diff --git a/ext/rcoremidi/client.c b/ext/rcoremidi/client.c index 5954447..8c9a687 100644 --- a/ext/rcoremidi/client.c +++ b/ext/rcoremidi/client.c @@ -138,7 +138,6 @@ VALUE client_alloc(VALUE klass) clientNode->transport = transport_alloc(); clientNode->in = malloc(sizeof(MIDIPortRef)); clientNode->out = malloc(sizeof(MIDIPortRef)); - clientNode->out = malloc(sizeof(MIDIPortRef)); clientNode->callback = malloc(sizeof(callback_t)); pthread_mutex_init(&clientNode->callback->mutex, NULL); pthread_cond_init(&clientNode->callback->cond, NULL); From 5032c3efd455426000daf4a070030920842fa540 Mon Sep 17 00:00:00 2001 From: Yann Marquet Date: Mon, 26 Mar 2018 10:54:07 +0200 Subject: [PATCH 04/11] sorted Song Pointer Position --- ext/rcoremidi/client.c | 33 ++++++++++++--------------------- 1 file changed, 12 insertions(+), 21 deletions(-) diff --git a/ext/rcoremidi/client.c b/ext/rcoremidi/client.c index 8c9a687..cd55626 100644 --- a/ext/rcoremidi/client.c +++ b/ext/rcoremidi/client.c @@ -3,7 +3,7 @@ static ByteCount max_packet_list_size = 65536; static unsigned long mspm = 60000000; static ByteCount note_on_packet_size = 2; static VALUE cb_thread; - +static int midi_beat_start = 0; pthread_mutex_t g_callback_mutex = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t g_callback_cond = PTHREAD_COND_INITIALIZER; @@ -82,8 +82,6 @@ static void midi_node_free(void *ptr) RCoremidiNode *tmp = ptr; if(tmp) { // May be (also) use MIDICLientDispose() from OSX API? - // Anyway is this usefull or should i just need to - // free the struct RCoremidiNode. Any just to make sure i free all free(tmp->client); free(tmp->transport); free(tmp->name); @@ -175,7 +173,10 @@ static void notifyProc(const MIDINotification *notification, void *refCon) } } - +static int calculate_current_tick(lsb, msb) { + int midi_beat_start = (lsb << 7) | msb; + return midi_beat_start * 6; +} static void MidiReadProc(const MIDIPacketList *pktlist, void *refCon, void *connRefCon) { pthread_mutex_lock(&g_callback_mutex); @@ -193,37 +194,27 @@ static void MidiReadProc(const MIDIPacketList *pktlist, void *refCon, void *conn /* printf("packet : %04x\n", packet->data[i]); */ switch(packet->data[i]) { case kMIDIStart: - printf("kMIDIStart\n"); + /* printf("kMIDIStart\n"); */ transport->state = kMIDIStart; - transport->tick_count++; transport->current_timestamp = mach_absolute_time(); - /* clientNode->callback->data = (void *)clientNode; */ - - - /* g_callback_queue_push(clientNode->callback); */ - break; case kMIDIStop: - printf("Stoping Client...\n"); + /* printf("Stoping Client...\n"); */ transport->state = kMIDIStop; break; case kMIDITick: transport->tick_count++; - /* printf("%d\n", transport->tick_count); */ - clientNode->callback->data = (void *)clientNode; + /* printf("C: tick_count %d\n", transport->tick_count); */ + clientNode->callback->data = (void *)clientNode; g_callback_queue_push(clientNode->callback); break; case kMIDISongPositionPointer: - printf("kMIDISongPositionPointer\n"); - - if (transport->state == kMIDIStop) { - transport->tick_count++; - } else { - reset_transport(transport); - } + transport->tick_count = calculate_current_tick(packet->data[i+1], packet->data[i+2]); break; + /* default: */ + /* printf("DEFAULT packet : %04x\n", packet->data[i]); */ } From 44e159f03b3808273bceefb8b3a4038b5cdb2bf3 Mon Sep 17 00:00:00 2001 From: Yann Marquet Date: Mon, 26 Mar 2018 12:35:37 +0100 Subject: [PATCH 05/11] [WIP] - generate rythms --- lib/rcoremidi/application.rb | 39 ++++++++++++++++----------------- lib/rcoremidi/client.rb | 12 +++++----- lib/rcoremidi/clip.rb | 16 ++++++++------ lib/rcoremidi/instrument.rb | 15 +++++-------- lib/rcoremidi/live.rb | 6 ++--- lib/rcoremidi/rythm_sequence.rb | 7 +++--- lib/rcoremidi/track.rb | 23 +++++++++---------- 7 files changed, 60 insertions(+), 58 deletions(-) diff --git a/lib/rcoremidi/application.rb b/lib/rcoremidi/application.rb index 1bbd29e..9890e91 100644 --- a/lib/rcoremidi/application.rb +++ b/lib/rcoremidi/application.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'yaml' require 'rcoremidi/app_pathname' require 'rcoremidi/client' @@ -5,9 +7,7 @@ require 'listen' module RCoreMidi - - class Configuration - + class Configuration # :nodoc: attr_accessor :bpm, :logger def initialize @@ -16,20 +16,18 @@ def initialize end end - class Application + class Application # :nodoc: attr_reader :root, :connections - def self.config @@config ||= Configuration.new end def initialize(app_path) self.root = AppPathname.new(app_path).expand_path - raise "Could not find application root" unless root.valid? + raise 'Could not find application root' unless root.valid? end def run(daemon_mode = false) - load_connections! load_clips! @@ -37,20 +35,19 @@ def run(daemon_mode = false) if daemon_mode Process.daemon - Process.setproctitle('arcx:live %s' % root) + Process.setproctitle(format('arcx:live %s', root)) write_pid_file redirect_output - client.midi_in = midi_in client.midi_out = midi_out client.create_live config.logger = Logger.new(root.log_file) - trap 'SIGINT', Proc.new { + trap 'SIGINT', proc { client.dispose - puts "quiting" + puts 'quiting' exit(0) } @@ -62,9 +59,9 @@ def run(daemon_mode = false) config.logger = Logger.new(STDOUT) - trap 'SIGINT', Proc.new { + trap 'SIGINT', proc { client.dispose - puts "quiting" + puts 'quiting' exit(0) } @@ -74,9 +71,9 @@ def run(daemon_mode = false) def stop if root.pid_file.exist? - Process.kill("QUIT", Integer(root.pid_file.read)) + Process.kill('QUIT', Integer(root.pid_file.read)) else - config.logger.info("No Arcx application seems to be running") + config.logger.info('No Arcx application seems to be running') end end @@ -89,10 +86,11 @@ def config end private + attr_writer :root, :connections def listeners - @listeners ||= Listen.to(root.clips_dir, root.instruments_dir) do |modified, added, removed| + @listeners ||= Listen.to(root.clips_dir, root.instruments_dir) do |modified, added, _removed| (modified + added).flat_map.each do |file| load file end @@ -101,14 +99,14 @@ def listeners def wait_for_start listeners.start - config.logger.info "Arcx live successfully started" + config.logger.info 'Arcx live successfully started' sleep end def redirect_output FileUtils.mkdir_p root.log_file.dirname # FileUtils.touch root.log_file - root.log_file.chmod(0644) + root.log_file.chmod(0o644) $stderr.reopen(root.log_file, 'a') $stdout.reopen($stderr) $stdout.sync = $stderr.sync = true @@ -119,7 +117,7 @@ def write_pid_file check_pid else root.pid_file.open(::File::CREAT | ::File::EXCL | ::File::WRONLY) do |f| - f.write("#{Process.pid}") + f.write(Process.pid.to_s) end at_exit do root.pid_file.delete if root.pid_file.exist? @@ -156,6 +154,7 @@ def client end def midi_in + pp connection @midi_in ||= MIDIObject.find_by_unique_id(connection.last['midi_in']) end @@ -168,7 +167,7 @@ def connection end def load_connections! - self.connections = YAML.load(root.connections_file.read) + self.connections = YAML.safe_load(root.connections_file.read) end def load_clips! diff --git a/lib/rcoremidi/client.rb b/lib/rcoremidi/client.rb index c2bb06a..b8b0ae7 100644 --- a/lib/rcoremidi/client.rb +++ b/lib/rcoremidi/client.rb @@ -1,12 +1,14 @@ -module RCoreMidi - class Client +# frozen_string_literal: true +module RCoreMidi + class Client # :nodoc: attr_accessor :midi_in, :midi_out def on_tick(current_tick) - # puts current_tick + puts current_tick to_send = live.generate_beats(current_tick).flatten.compact - # send_packets(midi_out, to_send) + ap to_send + send_packets(midi_out, to_send) end def create_live @@ -15,11 +17,11 @@ def create_live end private + attr_accessor :live def connect! connect_to midi_in end - end end diff --git a/lib/rcoremidi/clip.rb b/lib/rcoremidi/clip.rb index a1efb0c..d229812 100644 --- a/lib/rcoremidi/clip.rb +++ b/lib/rcoremidi/clip.rb @@ -1,20 +1,22 @@ +# frozen_string_literal: true + require 'observer' require 'rcoremidi/notifier' module RCoreMidi - - class Clip - include RCoreMidi::Registrable, Observable + class Clip # :nodoc: + include Observable + include RCoreMidi::Registrable attr_reader :rythm_sequences, :name, :notifier - def initialize(name, &block) - self.name = name + def initialize(name) + self.name = name self.rythm_sequences = [] end def note(pitch, beat_resolution, probabilities) - self.rythm_sequences << RythmSequence.new(pitch, beat_resolution, probabilities) + rythm_sequences << RythmSequence.new(pitch, beat_resolution, probabilities) end def load(&block) @@ -25,12 +27,12 @@ def load(&block) end private + attr_accessor :generator, :block attr_writer :rythm_sequences, :name, :notifier def log(msg) RCoreMidi::Application.config.logger.info msg end - end end diff --git a/lib/rcoremidi/instrument.rb b/lib/rcoremidi/instrument.rb index 1e29aee..c03c28a 100644 --- a/lib/rcoremidi/instrument.rb +++ b/lib/rcoremidi/instrument.rb @@ -1,10 +1,10 @@ +# frozen_string_literal: true + require 'rcoremidi/track' require 'observer' module RCoreMidi - - class Instrument - + class Instrument # :nodoc: include RCoreMidi::Registrable attr_reader :name def initialize(name, channel, &block) @@ -16,12 +16,11 @@ def initialize(name, channel, &block) def generate_beat(current_tick) track.generate(current_tick) end + def log(msg) RCoreMidi::Application.config.logger.info msg end - - def play(bar, clip_name, enable_probability = false) clp = clip(clip_name) clp.add_observer(self) @@ -39,7 +38,7 @@ def load instance_eval(&block) end - def update(clip) + def update(_clip) track.reset load end @@ -54,10 +53,8 @@ def track end def clip(name) - raise ArgumentError.new("Clip #{name} not found.") unless c = Clip[name] + raise ArgumentError, "Clip #{name} not found." unless c = Clip[name] c end - end - end diff --git a/lib/rcoremidi/live.rb b/lib/rcoremidi/live.rb index 72b158f..6a3e1de 100644 --- a/lib/rcoremidi/live.rb +++ b/lib/rcoremidi/live.rb @@ -1,8 +1,9 @@ +# frozen_string_literal: true + require 'rcoremidi/duration_calculator' module RCoreMidi - - class Live + class Live # :nodoc: attr_reader :instruments def generate_beats(current_tick) @@ -10,6 +11,5 @@ def generate_beats(current_tick) instrument.generate_beat(current_tick) end end - end end diff --git a/lib/rcoremidi/rythm_sequence.rb b/lib/rcoremidi/rythm_sequence.rb index 399d5fb..2fb3530 100644 --- a/lib/rcoremidi/rythm_sequence.rb +++ b/lib/rcoremidi/rythm_sequence.rb @@ -1,8 +1,9 @@ +# frozen_string_literal: true + require 'rcoremidi/probability_generator' module RCoreMidi - - class RythmSequence + class RythmSequence # :nodoc: def initialize(pitch, beat_resolution, probabilities) self.pitch = pitch self.beat_resolution = beat_resolution @@ -18,6 +19,7 @@ def generate(enable_probability, channel) end private + attr_accessor :pitch, :probabilities, :beat_resolution def probability_generator @@ -31,5 +33,4 @@ def duration_calculator @duration_calculator ||= DurationCalculator.new(RCoreMidi::Application.config.bpm) end end - end diff --git a/lib/rcoremidi/track.rb b/lib/rcoremidi/track.rb index 1e74d87..29e3759 100644 --- a/lib/rcoremidi/track.rb +++ b/lib/rcoremidi/track.rb @@ -1,10 +1,11 @@ +# frozen_string_literal: true + require 'rcoremidi/notifier' module RCoreMidi - - class Track - + class Track # :nodoc: attr_accessor :reset_at, :clips, :channel + PPQN = 96 def initialize(channel) self.reset_at = 0 @@ -12,17 +13,17 @@ def initialize(channel) end def generate(current_tick) - puts current_tick + # puts current_tick # if current_tick == reset_at # return @clips = {} && [] # end + bar = current_tick / PPQN + clip, enable_probability = bars[bar] - # clip, enable_probability = bars[bar] - - # return [] unless clip - # clip.rythm_sequences.map do |rythm_sequence| - # rythm_sequence.generate(enable_probability, channel).compact - # end.inject(:+) + return [] unless clip + clip.rythm_sequences.map do |rythm_sequence| + rythm_sequence.generate(enable_probability, channel).compact + end.inject(:+) end def play(bar, clip, enable_probability) @@ -34,11 +35,11 @@ def reset end private + attr_accessor :notifiers def bars @bars ||= {} end - end end From 740cd30f0f9446f959864b2f06a79a0ce509b08b Mon Sep 17 00:00:00 2001 From: yann marquet Date: Fri, 30 Mar 2018 11:02:30 +0100 Subject: [PATCH 06/11] create stop callback --- ext/rcoremidi/client.c | 15 ++++++++------- ext/rcoremidi/rcoremidi.h | 19 ++++++++++++------- lib/rcoremidi/client.rb | 2 +- lib/rcoremidi/registrable.rb | 1 - lib/rcoremidi/track.rb | 3 ++- 5 files changed, 23 insertions(+), 17 deletions(-) diff --git a/ext/rcoremidi/client.c b/ext/rcoremidi/client.c index cd55626..bcba5f2 100644 --- a/ext/rcoremidi/client.c +++ b/ext/rcoremidi/client.c @@ -7,10 +7,10 @@ static int midi_beat_start = 0; pthread_mutex_t g_callback_mutex = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t g_callback_cond = PTHREAD_COND_INITIALIZER; -callback_t *g_callback_queue = NULL; +callback_t *g_callback_queue = ULL; /* http://www.burgestrand.se//articles/asynchronous-callbacks-in-ruby-c-extensions/ */ -void g_callback_queue_push(callback_t *callback) +void g_callback_queue_push(tick_callback_t *callback) { callback->next = g_callback_queue; g_callback_queue = callback; @@ -113,10 +113,6 @@ RCoremidiNode * client_get_data(VALUE self) { static RCoreMidiTransport * reset_transport(RCoreMidiTransport * transport) { transport->tick_count = 0; - transport->bar = 1; - transport->quarter = 0; - transport->eigth = 0; - transport->sixteinth = 0; transport->state = kMIDIStop; return transport; } @@ -197,6 +193,10 @@ static void MidiReadProc(const MIDIPacketList *pktlist, void *refCon, void *conn /* printf("kMIDIStart\n"); */ transport->state = kMIDIStart; transport->current_timestamp = mach_absolute_time(); + + clientNode->callback->data = (void *)clientNode; + g_callback_queue_push(clientNode->callback); + break; case kMIDIStop: /* printf("Stoping Client...\n"); */ @@ -204,7 +204,7 @@ static void MidiReadProc(const MIDIPacketList *pktlist, void *refCon, void *conn break; case kMIDITick: transport->tick_count++; - /* printf("C: tick_count %d\n", transport->tick_count); */ + printf("C: tick_count %d\n", transport->tick_count); clientNode->callback->data = (void *)clientNode; g_callback_queue_push(clientNode->callback); @@ -412,6 +412,7 @@ VALUE client_init(VALUE self, VALUE name) } rb_iv_set(self, "@name", name); + rb_iv_set(self, "@started", QFalse); clientNode->rb_client_obj = self; diff --git a/ext/rcoremidi/rcoremidi.h b/ext/rcoremidi/rcoremidi.h index f68477b..944db7e 100644 --- a/ext/rcoremidi/rcoremidi.h +++ b/ext/rcoremidi/rcoremidi.h @@ -34,22 +34,27 @@ extern ID off_timestamp_intern; extern const rb_data_type_t midi_endpoint_data_t; extern const rb_data_type_t midi_object_data_t; -typedef struct callback_t { -pthread_mutex_t mutex; -pthread_cond_t cond; +typedef struct tick_callback_t { + pthread_mutex_t mutex; + pthread_cond_t cond; -struct callback_t *next; -void *data; -bool handled; -} callback_t; + struct callback_t *next; + void *data; +} tick_callback_t; + +typedef enum { + callback_t, midi_start_callback_t, midi_stop_callback_t +} waiting_callback_t; extern pthread_mutex_t g_callback_mutex; extern pthread_cond_t g_callback_cond; extern callback_t *g_callback_queue; +// callback queue typedef struct callback_waiting_t { callback_t *callback; bool abort; + enum type } callback_waiting_t; typedef struct midi_send_params_t { diff --git a/lib/rcoremidi/client.rb b/lib/rcoremidi/client.rb index b8b0ae7..d8e6e4d 100644 --- a/lib/rcoremidi/client.rb +++ b/lib/rcoremidi/client.rb @@ -7,7 +7,7 @@ class Client # :nodoc: def on_tick(current_tick) puts current_tick to_send = live.generate_beats(current_tick).flatten.compact - ap to_send + send_packets(midi_out, to_send) end diff --git a/lib/rcoremidi/registrable.rb b/lib/rcoremidi/registrable.rb index dd707f9..a48eb80 100644 --- a/lib/rcoremidi/registrable.rb +++ b/lib/rcoremidi/registrable.rb @@ -14,7 +14,6 @@ def self.included(klass) end klass.define_singleton_method :register do |*args, &block| - # return self[args.first].load && self[args.first] if self[args.first] klass.instance_variable_get(:@registry)[args.first] ||= new(*args, &block) klass.instance_variable_get(:@registry)[args.first].load(&block) end diff --git a/lib/rcoremidi/track.rb b/lib/rcoremidi/track.rb index 29e3759..a6dc098 100644 --- a/lib/rcoremidi/track.rb +++ b/lib/rcoremidi/track.rb @@ -13,7 +13,8 @@ def initialize(channel) end def generate(current_tick) - # puts current_tick + + puts current_tick # if current_tick == reset_at # return @clips = {} && [] # end From 75cc08d938ae52033c415c7aa748cbfde111cd55 Mon Sep 17 00:00:00 2001 From: yann marquet Date: Mon, 9 Apr 2018 20:49:37 +0100 Subject: [PATCH 07/11] fix stop and threading issue --- ext/rcoremidi/client.c | 67 ++++++++++++++++++++++++++------------- ext/rcoremidi/extconf.rb | 2 +- ext/rcoremidi/rcoremidi.h | 13 +++----- lib/rcoremidi/client.rb | 1 - lib/rcoremidi/track.rb | 2 +- 5 files changed, 51 insertions(+), 34 deletions(-) diff --git a/ext/rcoremidi/client.c b/ext/rcoremidi/client.c index bcba5f2..6086dc0 100644 --- a/ext/rcoremidi/client.c +++ b/ext/rcoremidi/client.c @@ -7,10 +7,10 @@ static int midi_beat_start = 0; pthread_mutex_t g_callback_mutex = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t g_callback_cond = PTHREAD_COND_INITIALIZER; -callback_t *g_callback_queue = ULL; +callback_t *g_callback_queue = NULL; -/* http://www.burgestrand.se//articles/asynchronous-callbacks-in-ruby-c-extensions/ */ -void g_callback_queue_push(tick_callback_t *callback) +/* http://www.burgestrand.se/articles/asynchronous-callbacks-in-ruby-c-extensions/ */ +void g_callback_queue_push(callback_t *callback) { callback->next = g_callback_queue; g_callback_queue = callback; @@ -51,7 +51,18 @@ static void stop_waiting_for_callback_signal(void *w) pthread_mutex_unlock(&g_callback_mutex); } +static VALUE handle_callback(void *callback) { + callback_t *cb = (callback_t*)callback; + RCoremidiNode *clientNode = (RCoremidiNode *)cb->data; + + rb_funcall(clientNode->rb_client_obj, rb_intern("on_tick"), 1, UINT2NUM(clientNode->transport->tick_count)); + + pthread_mutex_lock(&cb->mutex); + pthread_cond_signal(&g_callback_cond); + pthread_mutex_unlock(&cb->mutex); + return Qnil; +} static VALUE boot_callback_event_thread(void * data) { callback_waiting_t waiting = { .callback = NULL, .abort = false @@ -61,16 +72,7 @@ static VALUE boot_callback_event_thread(void * data) { rb_thread_call_without_gvl(wait_for_callback_signal, &waiting, stop_waiting_for_callback_signal, &waiting); if (waiting.callback) { - RCoremidiNode *clientNode = (RCoremidiNode *)waiting.callback->data; - pthread_mutex_lock(&waiting.callback->mutex); - rb_funcall( - clientNode->rb_client_obj, - rb_intern("on_tick"), - 1, - UINT2NUM(clientNode->transport->tick_count) - ); - /* printf ("TRANSPORT: %d \n", clientNode->transport->tick_count); */ - pthread_mutex_unlock(&waiting.callback->mutex); + rb_thread_create(handle_callback, waiting.callback); } } return Qnil; @@ -173,9 +175,8 @@ static int calculate_current_tick(lsb, msb) { int midi_beat_start = (lsb << 7) | msb; return midi_beat_start * 6; } -static void MidiReadProc(const MIDIPacketList *pktlist, void *refCon, void *connRefCon) { - pthread_mutex_lock(&g_callback_mutex); +static void MidiReadProc(const MIDIPacketList *pktlist, void *refCon, void *connRefCon) { MIDIPacket *packet = (MIDIPacket *)pktlist->packet; unsigned int j; int i; @@ -194,27 +195,51 @@ static void MidiReadProc(const MIDIPacketList *pktlist, void *refCon, void *conn transport->state = kMIDIStart; transport->current_timestamp = mach_absolute_time(); - clientNode->callback->data = (void *)clientNode; - g_callback_queue_push(clientNode->callback); + /* pthread_mutex_lock(&g_callback_mutex); */ + /* clientNode->callback->data = (void *)clientNode; */ + /* g_callback_queue_push(clientNode->callback); */ + /* pthread_mutex_unlock(&g_callback_mutex); */ + /* pthread_cond_signal(&g_callback_cond); */ + break; case kMIDIStop: /* printf("Stoping Client...\n"); */ transport->state = kMIDIStop; + + /* callback_waiting_t *waiting; */ + + /* int callback_count = 0; */ + /* callback_t *callback; */ + + /* /\* while ((callback = g_callback_queue_pop())) { *\/ */ + /* /\* callback_count++; *\/ */ + /* /\* printf("%d callbacks left\n", callback_count); *\/ */ + /* /\* } *\/ */ + /* clientNode->callback->data = (void *)clientNode; */ + + /* pthread_mutex_lock(&g_callback_mutex); */ + /* g_callback_queue_push(clientNode->callback); */ + /* pthread_mutex_unlock(&g_callback_mutex); */ + /* pthread_cond_signal(&g_callback_cond); */ + break; case kMIDITick: transport->tick_count++; - printf("C: tick_count %d\n", transport->tick_count); + /* printf("C: tick_count %d\n", transport->tick_count); */ + pthread_mutex_lock(&g_callback_mutex); clientNode->callback->data = (void *)clientNode; g_callback_queue_push(clientNode->callback); + pthread_mutex_unlock(&g_callback_mutex); + pthread_cond_signal(&g_callback_cond); break; case kMIDISongPositionPointer: transport->tick_count = calculate_current_tick(packet->data[i+1], packet->data[i+2]); break; /* default: */ - /* printf("DEFAULT packet : %04x\n", packet->data[i]); */ + /* printf("DEFAULT packet : %04x\n", packet->data[i]); */ } @@ -229,8 +254,6 @@ static void MidiReadProc(const MIDIPacketList *pktlist, void *refCon, void *conn packet = MIDIPacketNext(packet); } - pthread_mutex_unlock(&g_callback_mutex); - pthread_cond_signal(&g_callback_cond); } VALUE connect_to(VALUE self, VALUE source) @@ -412,7 +435,7 @@ VALUE client_init(VALUE self, VALUE name) } rb_iv_set(self, "@name", name); - rb_iv_set(self, "@started", QFalse); + rb_iv_set(self, "@started", Qfalse); clientNode->rb_client_obj = self; diff --git a/ext/rcoremidi/extconf.rb b/ext/rcoremidi/extconf.rb index 7f4ac0b..20a6dff 100644 --- a/ext/rcoremidi/extconf.rb +++ b/ext/rcoremidi/extconf.rb @@ -2,7 +2,7 @@ $CFLAGS = "-Wno-error=shorten-64-to-32 -g" $CPPFLAGS += " -g " -$CPPFLAGS += "-I/Users/yann/.rbenv/versions/2.1.1/include/ruby-2.1.0 -I/System/Library/Frameworks/CoreMIDI.framework/Headers " +$CPPFLAGS += "-I/System/Library/Frameworks/CoreMIDI.framework/Headers " $LDFLAGS += " -F/System/Library/Frameworks -framework CoreMIDI -framework CoreFoundation -framework CoreAudio -framework Carbon" dir_config("rcoremidi") create_makefile("rcoremidi") diff --git a/ext/rcoremidi/rcoremidi.h b/ext/rcoremidi/rcoremidi.h index 944db7e..e053295 100644 --- a/ext/rcoremidi/rcoremidi.h +++ b/ext/rcoremidi/rcoremidi.h @@ -1,6 +1,6 @@ #include -#include -#include +#include "ruby.h" +#include "ruby/thread.h" #include #include #include @@ -34,17 +34,13 @@ extern ID off_timestamp_intern; extern const rb_data_type_t midi_endpoint_data_t; extern const rb_data_type_t midi_object_data_t; -typedef struct tick_callback_t { +typedef struct callback_t { pthread_mutex_t mutex; pthread_cond_t cond; struct callback_t *next; void *data; -} tick_callback_t; - -typedef enum { - callback_t, midi_start_callback_t, midi_stop_callback_t -} waiting_callback_t; +} callback_t; extern pthread_mutex_t g_callback_mutex; extern pthread_cond_t g_callback_cond; @@ -54,7 +50,6 @@ extern callback_t *g_callback_queue; typedef struct callback_waiting_t { callback_t *callback; bool abort; - enum type } callback_waiting_t; typedef struct midi_send_params_t { diff --git a/lib/rcoremidi/client.rb b/lib/rcoremidi/client.rb index d8e6e4d..aafdf56 100644 --- a/lib/rcoremidi/client.rb +++ b/lib/rcoremidi/client.rb @@ -5,7 +5,6 @@ class Client # :nodoc: attr_accessor :midi_in, :midi_out def on_tick(current_tick) - puts current_tick to_send = live.generate_beats(current_tick).flatten.compact send_packets(midi_out, to_send) diff --git a/lib/rcoremidi/track.rb b/lib/rcoremidi/track.rb index a6dc098..4f0baeb 100644 --- a/lib/rcoremidi/track.rb +++ b/lib/rcoremidi/track.rb @@ -14,7 +14,7 @@ def initialize(channel) def generate(current_tick) - puts current_tick + # puts current_tick # if current_tick == reset_at # return @clips = {} && [] # end From dd887478e4d4d75d6833de01492ee88917a0df34 Mon Sep 17 00:00:00 2001 From: yann marquet Date: Wed, 11 Apr 2018 15:46:11 +0100 Subject: [PATCH 08/11] removed randomness generator for ease to refactor now clips generate a matrix with notes to play for each midi beat clock refactored track to merge notes from clips on load --- lib/rcoremidi/client.rb | 10 ++-- lib/rcoremidi/clip.rb | 11 ++++- lib/rcoremidi/duration_calculator.rb | 4 +- lib/rcoremidi/instrument.rb | 30 ++++++------ lib/rcoremidi/live.rb | 24 ++++++++-- lib/rcoremidi/note.rb | 4 +- lib/rcoremidi/rythm_sequence.rb | 20 ++++---- lib/rcoremidi/track.rb | 38 +++++---------- spec/fixtures/clips/piano.rb | 5 ++ spec/fixtures/instruments/piano.rb | 3 ++ spec/rcoremidi/clip_spec.rb | 33 ++++++++++--- spec/rcoremidi/live_spec.rb | 38 ++++++++++----- spec/rcoremidi/track_spec.rb | 71 ++++++++++++++-------------- 13 files changed, 175 insertions(+), 116 deletions(-) create mode 100644 spec/fixtures/clips/piano.rb create mode 100644 spec/fixtures/instruments/piano.rb diff --git a/lib/rcoremidi/client.rb b/lib/rcoremidi/client.rb index aafdf56..4503491 100644 --- a/lib/rcoremidi/client.rb +++ b/lib/rcoremidi/client.rb @@ -4,15 +4,19 @@ module RCoreMidi class Client # :nodoc: attr_accessor :midi_in, :midi_out - def on_tick(current_tick) - to_send = live.generate_beats(current_tick).flatten.compact - + def on_tick(midi_beat_clock) + puts midi_beat_clock + # return unless bar_start?(midi_beat_clock) + byebug + to_send = live.beats_for(current_tick) + ap to_send send_packets(midi_out, to_send) end def create_live connect! self.live = Live.new + live.load end private diff --git a/lib/rcoremidi/clip.rb b/lib/rcoremidi/clip.rb index d229812..6e55004 100644 --- a/lib/rcoremidi/clip.rb +++ b/lib/rcoremidi/clip.rb @@ -15,8 +15,11 @@ def initialize(name) self.rythm_sequences = [] end - def note(pitch, beat_resolution, probabilities) - rythm_sequences << RythmSequence.new(pitch, beat_resolution, probabilities) + def note(pitch, rhythm_pattern) + rhythm_pattern = RythmSequence.new(pitch, rhythm_pattern) + rhythm_pattern.generate.each do |note, i| + beats[i] << note if note + end end def load(&block) @@ -26,6 +29,10 @@ def load(&block) notify_observers(self) end + def beats + @notes ||= Array.new(96) { [] } + end + private attr_accessor :generator, :block diff --git a/lib/rcoremidi/duration_calculator.rb b/lib/rcoremidi/duration_calculator.rb index 9646139..76c27a9 100644 --- a/lib/rcoremidi/duration_calculator.rb +++ b/lib/rcoremidi/duration_calculator.rb @@ -13,8 +13,8 @@ def initialize(bpm, ppqn = 96) # seixteenth note = 240 ticks def timestamps_for(note_index, resolution = 16) # A probabilities array of length 16 means - # our current resolution is an eigthth note - # 48 ticks is our current resolution + # our current resolution is an 16th note + on = (mpt * 6 * note_index).round [on, (on + default_note_off_offset).round] end diff --git a/lib/rcoremidi/instrument.rb b/lib/rcoremidi/instrument.rb index c03c28a..926799c 100644 --- a/lib/rcoremidi/instrument.rb +++ b/lib/rcoremidi/instrument.rb @@ -7,6 +7,7 @@ module RCoreMidi class Instrument # :nodoc: include RCoreMidi::Registrable attr_reader :name + def initialize(name, channel, &block) self.name = name self.channel = channel @@ -14,24 +15,17 @@ def initialize(name, channel, &block) end def generate_beat(current_tick) - track.generate(current_tick) + track.bars[current_tick] end def log(msg) RCoreMidi::Application.config.logger.info msg end - def play(bar, clip_name, enable_probability = false) - clp = clip(clip_name) - clp.add_observer(self) - if bar.is_a? Range - bar.each do |bar_index| - track.play(bar_index, clp, enable_probability) - end - track.reset_at = bar.max - else - track.play(bar, clp, enable_probability) - end + def play(bar, clip_name) + clip = find_clip(clip_name) + clip.add_observer(self) + track.play(bar, clip, channel) end def load @@ -43,18 +37,22 @@ def update(_clip) load end + def bar(number) + track.bars[number] + end + private attr_accessor :channel, :file, :block attr_writer :tracks, :name def track - @track ||= Track.new(channel) + @track ||= Track.new end - def clip(name) - raise ArgumentError, "Clip #{name} not found." unless c = Clip[name] - c + def find_clip(name) + raise ArgumentError, "Clip #{name} not found." unless clip = Clip[name] + clip end end end diff --git a/lib/rcoremidi/live.rb b/lib/rcoremidi/live.rb index 6a3e1de..13048bf 100644 --- a/lib/rcoremidi/live.rb +++ b/lib/rcoremidi/live.rb @@ -6,10 +6,28 @@ module RCoreMidi class Live # :nodoc: attr_reader :instruments - def generate_beats(current_tick) - Instrument.all.flat_map do |instrument| - instrument.generate_beat(current_tick) + PPQN = 24 + + def beats_for(current_tick) + beats[current_tick] + end + + def load(current_bar = 1) + beats.each_with_index do |beat, i| + Instrument.all.each do |instrument| + + instrument_beats = instrument.bar(current_bar) + begin + beat << instrument_beats[i] unless instrument_beats[i].empty? + rescue + byebug + end + end end end + + def beats + @beats ||= Array.new(96) { [] } + end end end diff --git a/lib/rcoremidi/note.rb b/lib/rcoremidi/note.rb index c8a3da7..e7f1219 100644 --- a/lib/rcoremidi/note.rb +++ b/lib/rcoremidi/note.rb @@ -2,8 +2,8 @@ module RCoreMidi class Note - attr_accessor :note, :velocity, :channel, :status_byte, :on_timestamp, :off_timestamp - private :note=, :velocity=, :channel=, :status_byte=, :on_timestamp= + attr_accessor :note, :velocity, :channel, :status_byte, :on_timestamp, :off_timestamp, :channel + private :note=, :velocity=, :status_byte=, :on_timestamp= NOTE_ON = 0x90 NOTE_OFF = 0x80 diff --git a/lib/rcoremidi/rythm_sequence.rb b/lib/rcoremidi/rythm_sequence.rb index 2fb3530..d4fe28c 100644 --- a/lib/rcoremidi/rythm_sequence.rb +++ b/lib/rcoremidi/rythm_sequence.rb @@ -4,23 +4,27 @@ module RCoreMidi class RythmSequence # :nodoc: - def initialize(pitch, beat_resolution, probabilities) - self.pitch = pitch - self.beat_resolution = beat_resolution - self.probabilities = probabilities + + PULSE_PER_BAR = 96 + + def initialize(pitch, probabilities, rhythm = probabilities.size) + self.pitch = pitch + self.delta = PULSE_PER_BAR / rhythm + self.probabilities = probabilities end - def generate(enable_probability, channel) + def generate probabilities.map.with_index do |probability, i| - next unless probability_generator[enable_probability].play?(probability) + next [nil, i] if probability.zero? + # probability_generator[enable_probability].play?(probability) - Note.new(pitch, *duration_calculator.timestamps_for(i), channel) + [Note.new(pitch, *duration_calculator.timestamps_for(i), nil), delta * i] end end private - attr_accessor :pitch, :probabilities, :beat_resolution + attr_accessor :pitch, :probabilities, :delta def probability_generator @probability_generator ||= { diff --git a/lib/rcoremidi/track.rb b/lib/rcoremidi/track.rb index 4f0baeb..a1de3ea 100644 --- a/lib/rcoremidi/track.rb +++ b/lib/rcoremidi/track.rb @@ -5,42 +5,28 @@ module RCoreMidi class Track # :nodoc: attr_accessor :reset_at, :clips, :channel - PPQN = 96 - def initialize(channel) - self.reset_at = 0 - self.channel = channel + def play(bar, clip, channel = 0) + if bars[bar].nil? + clip.beats.each { |notes| notes.each { |note| note.channel = channel } } + bars[bar] = clip.beats + else + bars[bar].each_with_index do |existing_notes, i| + clip.beats[i].each { |note| note.channel = channel } + existing_notes |= clip.beats[i] + end + end end - def generate(current_tick) - - # puts current_tick - # if current_tick == reset_at - # return @clips = {} && [] - # end - bar = current_tick / PPQN - clip, enable_probability = bars[bar] - - return [] unless clip - clip.rythm_sequences.map do |rythm_sequence| - rythm_sequence.generate(enable_probability, channel).compact - end.inject(:+) - end - - def play(bar, clip, enable_probability) - bars[bar] = [clip, enable_probability] + def bars + @bars ||= {} end def reset @bars = {} end - private attr_accessor :notifiers - - def bars - @bars ||= {} - end end end diff --git a/spec/fixtures/clips/piano.rb b/spec/fixtures/clips/piano.rb new file mode 100644 index 0000000..bd05371 --- /dev/null +++ b/spec/fixtures/clips/piano.rb @@ -0,0 +1,5 @@ +RCoreMidi::Clip.register :piano do + note 'E5', [1, 0, 0] + note 'F#5', [0, 1, 0] + note 'G#5', [0, 0, 1] +end diff --git a/spec/fixtures/instruments/piano.rb b/spec/fixtures/instruments/piano.rb new file mode 100644 index 0000000..c21a553 --- /dev/null +++ b/spec/fixtures/instruments/piano.rb @@ -0,0 +1,3 @@ +RCoreMidi::Instrument.register :piano, 1 do + play 1, :piano +end diff --git a/spec/rcoremidi/clip_spec.rb b/spec/rcoremidi/clip_spec.rb index 3cd4208..fb606db 100644 --- a/spec/rcoremidi/clip_spec.rb +++ b/spec/rcoremidi/clip_spec.rb @@ -13,16 +13,35 @@ it_behaves_like 'registrable', [:clip_name] describe '#note' do - let(:rythm_sequence) { instance_double(RCoreMidi::RythmSequence) } - let(:pitch) { 'E5' } - let(:notes) { [1,0,0,0] * 4 } - let(:beat_resolution) { 16 } + let(:e5) { 'E5' } + let(:d5) { 'D5' } + let(:e5_rhythm) { [1,0,1,0] * 4 } + let(:d5_rhythm) { [1,1,1] * 4 } + let(:ppqn) { 24 } it 'parses RythmSequence' do - expect(RCoreMidi::RythmSequence).to receive(:new).with(pitch, beat_resolution, notes).and_return(rythm_sequence) - subject.note(pitch, beat_resolution, notes) + subject.note(e5, e5_rhythm) + subject.note(d5, d5_rhythm) - expect(subject.rythm_sequences).to eq([rythm_sequence]) + expect(subject.beats[0]).to include(RCoreMidi::Note.new('D5'), RCoreMidi::Note.new('E5')) + expect(subject.beats[8]).to include(RCoreMidi::Note.new('D5')) + expect(subject.beats[12]).to include(RCoreMidi::Note.new('E5')) + expect(subject.beats[16]).to include(RCoreMidi::Note.new('D5')) + + expect(subject.beats[0 + ppqn]).to include(RCoreMidi::Note.new('D5'), RCoreMidi::Note.new('E5')) + expect(subject.beats[8 + ppqn]).to include(RCoreMidi::Note.new('D5')) + expect(subject.beats[12 + ppqn]).to include(RCoreMidi::Note.new('E5')) + expect(subject.beats[16 + ppqn]).to include(RCoreMidi::Note.new('D5')) + + expect(subject.beats[0 + ppqn * 2]).to include(RCoreMidi::Note.new('D5'), RCoreMidi::Note.new('E5')) + expect(subject.beats[8 + ppqn * 2]).to include(RCoreMidi::Note.new('D5')) + expect(subject.beats[12 + ppqn * 2]).to include(RCoreMidi::Note.new('E5')) + expect(subject.beats[16 + ppqn * 2]).to include(RCoreMidi::Note.new('D5')) + + expect(subject.beats[0 + ppqn * 3]).to include(RCoreMidi::Note.new('D5'), RCoreMidi::Note.new('E5')) + expect(subject.beats[8 + ppqn * 3]).to include(RCoreMidi::Note.new('D5')) + expect(subject.beats[12 + ppqn * 3]).to include(RCoreMidi::Note.new('E5')) + expect(subject.beats[16 + ppqn * 3]).to include(RCoreMidi::Note.new('D5')) end end end diff --git a/spec/rcoremidi/live_spec.rb b/spec/rcoremidi/live_spec.rb index 74284b6..fc39920 100644 --- a/spec/rcoremidi/live_spec.rb +++ b/spec/rcoremidi/live_spec.rb @@ -6,9 +6,13 @@ let(:clip_dir) { File.join('spec', 'fixtures', 'clips') } let(:instruments_dir) { File.join('spec', 'fixtures', 'instruments') } - subject { described_class.new(bpm, clip_dir, instruments_dir) } + # subject { described_class.new(bpm, clip_dir, instruments_dir) } - describe '#load_clips' do + before do + Dir["#{clip_dir}/**/*.rb"].each { |f| load f } + Dir["#{instruments_dir}/**/*.rb"].each { |f| load f } + end + describe '#load_clip' do it 'loads clips from file' do subject expect(RCoreMidi::Clip[:drum]).to be_instance_of(RCoreMidi::Clip) @@ -22,15 +26,27 @@ end end - describe '#generate_beats' do - let(:duration_calculator) { RCoreMidi::DurationCalculator.new(120) } - it "return notes" do - expect(subject.generate_beats(1)).to eq([ - RCoreMidi::Note.new('E5', *duration_calculator.timestamps_for(0)), - RCoreMidi::Note.new('E5', *duration_calculator.timestamps_for(4)), - RCoreMidi::Note.new('E5', *duration_calculator.timestamps_for(8)), - RCoreMidi::Note.new('E5', *duration_calculator.timestamps_for(12)), - ]) + describe '#load' do + + it "loads the given bar" do + subject.load + ap subject.beats + # expect(subject.bars).to eq({}) + # RCoreMidi::Note.new('E5', *duration_calculator.timestamps_for(0)), + # RCoreMidi::Note.new('E5', *duration_calculator.timestamps_for(4)), + # RCoreMidi::Note.new('E5', *duration_calculator.timestamps_for(8)), + # RCoreMidi::Note.new('E5', *duration_calculator.timestamps_for(12)), + end + end + + describe '#sequence!' do + let(:sequencer) { RCoreMidi::Sequencer } + before do + expect(RCoreMidi::Sequencer).to receive(:new).and_return(sequencer) + end + + it 'prepares the first bar sequence' do + subject.sequence! end end end diff --git a/spec/rcoremidi/track_spec.rb b/spec/rcoremidi/track_spec.rb index 1bf158b..875b139 100644 --- a/spec/rcoremidi/track_spec.rb +++ b/spec/rcoremidi/track_spec.rb @@ -2,48 +2,47 @@ RSpec.describe RCoreMidi::Track do - let(:clip) do - RCoreMidi::Clip.new(:drum) do - note 'E5', [1, 0, 0, 0] * 4 + before do + RCoreMidi::Clip.register(clip_name) do + note 'E5', [1,0,1,0] * 4 + note 'D5', [1,1,1] * 4 end end - let(:bar) { 1 } - let(:enable_probability) { false } - let(:clips) { {} } + let(:ppqn) { 24 } + let(:clip_name) { :drum } + let(:clip) { RCoreMidi::Clip[clip_name] } describe '#play' do - - it "stores what clip to play for a given bar and configuration" do - expect(subject).to receive(:clips).and_return(clips) - - subject.play bar, clip, enable_probability - - expect(clips).to eq(bar => [clip, enable_probability]) - end - - end - - describe '#generate' do - - let(:duration_calculator) { RCoreMidi::DurationCalculator.new(120) } - - before do - subject.play(1, clip, enable_probability) - end - - context 'with no probability generator disabled' do - it "generates notes" do - expect(subject.generate(1, duration_calculator)).to eq( - [ - RCoreMidi::Note.new('E5', *duration_calculator.timestamps_for(0)), - RCoreMidi::Note.new('E5', *duration_calculator.timestamps_for(4)), - RCoreMidi::Note.new('E5', *duration_calculator.timestamps_for(8)), - RCoreMidi::Note.new('E5', *duration_calculator.timestamps_for(12)), - ] - ) + context 'when given a single bar' do + let(:bar) { 1 } + + context 'when there are not notes yet' do + it "add clip notes to given bar" do + subject.play bar, clip + + expect(subject.bars[bar]).to eq(clip.beats) + expect(subject.bars[bar][0]).to include(RCoreMidi::Note.new('D5'), RCoreMidi::Note.new('E5')) + expect(subject.bars[bar][8]).to include(RCoreMidi::Note.new('D5')) + expect(subject.bars[bar][12]).to include(RCoreMidi::Note.new('E5')) + expect(subject.bars[bar][16]).to include(RCoreMidi::Note.new('D5')) + + expect(subject.bars[bar][0 + ppqn]).to include(RCoreMidi::Note.new('D5'), RCoreMidi::Note.new('E5')) + expect(subject.bars[bar][8 + ppqn]).to include(RCoreMidi::Note.new('D5')) + expect(subject.bars[bar][12 + ppqn]).to include(RCoreMidi::Note.new('E5')) + expect(subject.bars[bar][16 + ppqn]).to include(RCoreMidi::Note.new('D5')) + + expect(subject.bars[bar][0 + ppqn * 2]).to include(RCoreMidi::Note.new('D5'), RCoreMidi::Note.new('E5')) + expect(subject.bars[bar][8 + ppqn * 2]).to include(RCoreMidi::Note.new('D5')) + expect(subject.bars[bar][12 + ppqn * 2]).to include(RCoreMidi::Note.new('E5')) + expect(subject.bars[bar][16 + ppqn * 2]).to include(RCoreMidi::Note.new('D5')) + + expect(subject.bars[bar][0 + ppqn * 3]).to include(RCoreMidi::Note.new('D5'), RCoreMidi::Note.new('E5')) + expect(subject.bars[bar][8 + ppqn * 3]).to include(RCoreMidi::Note.new('D5')) + expect(subject.bars[bar][12 + ppqn * 3]).to include(RCoreMidi::Note.new('E5')) + expect(subject.bars[bar][16 + ppqn * 3]).to include(RCoreMidi::Note.new('D5')) + end end end end - end From 046330676ef2d75d34c487e6cdf289a9eca95c37 Mon Sep 17 00:00:00 2001 From: yann marquet Date: Thu, 12 Apr 2018 22:52:58 +0100 Subject: [PATCH 09/11] fix calculation of song position pointer --- ext/rcoremidi/client.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/rcoremidi/client.c b/ext/rcoremidi/client.c index 6086dc0..c4d5058 100644 --- a/ext/rcoremidi/client.c +++ b/ext/rcoremidi/client.c @@ -172,7 +172,7 @@ static void notifyProc(const MIDINotification *notification, void *refCon) } static int calculate_current_tick(lsb, msb) { - int midi_beat_start = (lsb << 7) | msb; + int midi_beat_start = (msb << 7) | lsb; return midi_beat_start * 6; } From 559e1f0f776be23254f6f559679206d31b19126e Mon Sep 17 00:00:00 2001 From: yann marquet Date: Tue, 17 Apr 2018 22:26:00 +0100 Subject: [PATCH 10/11] used thread pool to fire on_tick callback Need to rename on_tick => on_midi_clock --- Gemfile.lock | 10 ++-- ext/rcoremidi/client.c | 72 ++++++++++++---------------- ext/rcoremidi/rcoremidi.c | 9 ++++ ext/rcoremidi/rcoremidi.h | 5 ++ lib/rcoremidi.rb | 1 + lib/rcoremidi/application.rb | 1 - lib/rcoremidi/client.rb | 11 +++-- lib/rcoremidi/duration_calculator.rb | 3 +- lib/rcoremidi/live.rb | 9 ++-- rcoremidi.gemspec | 1 + 10 files changed, 65 insertions(+), 57 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 01d8a46..7e07e6d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -7,6 +7,7 @@ PATH remote: . specs: rcoremidi (0.0.1) + concurrent-ruby listen thor @@ -27,6 +28,7 @@ GEM childprocess (0.8.0) ffi (~> 1.0, >= 1.0.11) coderay (1.1.2) + concurrent-ruby (1.0.5) contracts (0.16.0) cucumber (3.0.0) builder (>= 2.1.2) @@ -47,9 +49,10 @@ GEM diff-lcs (1.3) ffi (1.9.18) gherkin (4.1.3) - listen (3.0.8) + listen (3.1.5) rb-fsevent (~> 0.9, >= 0.9.4) rb-inotify (~> 0.9, >= 0.9.7) + ruby_dep (~> 1.2) method_source (0.9.0) multi_json (1.12.2) multi_test (0.1.2) @@ -62,7 +65,7 @@ GEM rake (12.3.0) rake-compiler (1.0.4) rake - rb-fsevent (0.10.2) + rb-fsevent (0.10.3) rb-inotify (0.9.10) ffi (>= 0.5.0, < 2) rdoc (4.3.0) @@ -82,6 +85,7 @@ GEM diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.7.0) rspec-support (3.7.0) + ruby_dep (1.5.0) thor (0.20.0) PLATFORMS @@ -99,4 +103,4 @@ DEPENDENCIES rspec-its BUNDLED WITH - 1.16.0 + 1.16.1 diff --git a/ext/rcoremidi/client.c b/ext/rcoremidi/client.c index c4d5058..f4c3a74 100644 --- a/ext/rcoremidi/client.c +++ b/ext/rcoremidi/client.c @@ -4,7 +4,7 @@ static unsigned long mspm = 60000000; static ByteCount note_on_packet_size = 2; static VALUE cb_thread; static int midi_beat_start = 0; - +static int last_midi_beat = 0; pthread_mutex_t g_callback_mutex = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t g_callback_cond = PTHREAD_COND_INITIALIZER; callback_t *g_callback_queue = NULL; @@ -51,18 +51,27 @@ static void stop_waiting_for_callback_signal(void *w) pthread_mutex_unlock(&g_callback_mutex); } -static VALUE handle_callback(void *callback) { +static VALUE handle_callback(VALUE block_arg, VALUE data, int argc, VALUE* argv) { - callback_t *cb = (callback_t*)callback; - RCoremidiNode *clientNode = (RCoremidiNode *)cb->data; + printf("Handle callback: args: %d\n", argc); + /* callback_t *cb = (callback_t*)callback; */ + /* RCoremidiNode *clientNode = (RCoremidiNode *)cb->data; */ - rb_funcall(clientNode->rb_client_obj, rb_intern("on_tick"), 1, UINT2NUM(clientNode->transport->tick_count)); + /* rb_funcall(clientNode->rb_client_obj, rb_intern("on_tick"), 1, UINT2NUM(clientNode->transport->tick_count)); */ - pthread_mutex_lock(&cb->mutex); - pthread_cond_signal(&g_callback_cond); - pthread_mutex_unlock(&cb->mutex); + /* pthread_mutex_lock(&cb->mutex); */ + /* pthread_cond_signal(&g_callback_cond); */ + /* pthread_mutex_unlock(&cb->mutex); */ return Qnil; } + +/* VALUE my_block(VALUE block_arg, VALUE data, int argc, VALUE* argv) */ +/* { */ +/* /\* block_arg will be the first yielded value *\/ */ +/* /\* data will be the last argument you passed to rb_block_call *\/ */ +/* /\* if multiple values are yielded, use argc/argv to access them *\/ */ +/* } */ + static VALUE boot_callback_event_thread(void * data) { callback_waiting_t waiting = { .callback = NULL, .abort = false @@ -72,7 +81,10 @@ static VALUE boot_callback_event_thread(void * data) { rb_thread_call_without_gvl(wait_for_callback_signal, &waiting, stop_waiting_for_callback_signal, &waiting); if (waiting.callback) { - rb_thread_create(handle_callback, waiting.callback); + callback_t *cb = (callback_t*)waiting.callback; + RCoremidiNode *clientNode = (RCoremidiNode *)cb->data; + VALUE pool = rb_iv_get(clientNode->rb_client_obj, "@pool"); + rb_block_call(pool, rb_intern("post"), 0, NULL, handle_callback, Qnil); } } return Qnil; @@ -188,45 +200,21 @@ static void MidiReadProc(const MIDIPacketList *pktlist, void *refCon, void *conn for (i = 0; i < packet->length; ++i) { - /* printf("packet : %04x\n", packet->data[i]); */ switch(packet->data[i]) { case kMIDIStart: - /* printf("kMIDIStart\n"); */ transport->state = kMIDIStart; transport->current_timestamp = mach_absolute_time(); - - /* pthread_mutex_lock(&g_callback_mutex); */ - /* clientNode->callback->data = (void *)clientNode; */ - /* g_callback_queue_push(clientNode->callback); */ - /* pthread_mutex_unlock(&g_callback_mutex); */ - /* pthread_cond_signal(&g_callback_cond); */ - - + transport->tick_count = -1; + break; + case kMIDIContinue: + transport->tick_count = last_midi_beat - 1; break; case kMIDIStop: - /* printf("Stoping Client...\n"); */ transport->state = kMIDIStop; - - /* callback_waiting_t *waiting; */ - - /* int callback_count = 0; */ - /* callback_t *callback; */ - - /* /\* while ((callback = g_callback_queue_pop())) { *\/ */ - /* /\* callback_count++; *\/ */ - /* /\* printf("%d callbacks left\n", callback_count); *\/ */ - /* /\* } *\/ */ - /* clientNode->callback->data = (void *)clientNode; */ - - /* pthread_mutex_lock(&g_callback_mutex); */ - /* g_callback_queue_push(clientNode->callback); */ - /* pthread_mutex_unlock(&g_callback_mutex); */ - /* pthread_cond_signal(&g_callback_cond); */ - break; case kMIDITick: transport->tick_count++; - /* printf("C: tick_count %d\n", transport->tick_count); */ + printf("C: tick_count %d\n", transport->tick_count); pthread_mutex_lock(&g_callback_mutex); clientNode->callback->data = (void *)clientNode; @@ -236,10 +224,11 @@ static void MidiReadProc(const MIDIPacketList *pktlist, void *refCon, void *conn break; case kMIDISongPositionPointer: - transport->tick_count = calculate_current_tick(packet->data[i+1], packet->data[i+2]); + last_midi_beat = calculate_current_tick(packet->data[i+1], packet->data[i+2]); + /* packet = MIDIPacketNext(packet); */ break; - /* default: */ - /* printf("DEFAULT packet : %04x\n", packet->data[i]); */ + default: + printf("DEFAULT packet : %04x\n", packet->data[i]); } @@ -444,6 +433,7 @@ VALUE client_init(VALUE self, VALUE name) rb_funcall(cb_thread, rb_intern("abort_on_exception="), 1, Qtrue); rb_iv_set(self, "@cb_thread", cb_thread); + rb_iv_set(self, "@pool", rb_funcall(rb_cCachedThreadPool, new_intern, 0, Qnil)); return self; } diff --git a/ext/rcoremidi/rcoremidi.c b/ext/rcoremidi/rcoremidi.c index bf0a52e..c177363 100644 --- a/ext/rcoremidi/rcoremidi.c +++ b/ext/rcoremidi/rcoremidi.c @@ -1,5 +1,6 @@ #include "rcoremidi.h" +VALUE rb_cCachedThreadPool; VALUE rb_mRCOREMIDI; VALUE rb_cMIDIObject; VALUE rb_cDevice; @@ -130,5 +131,13 @@ Init_rcoremidi() rb_define_method(rb_cClient, "send_packets", send_packets, 2); rb_define_attr(rb_cClient, "name", 1, 1); rb_define_attr(rb_cClient, "is_connected", 1, 1); + rb_define_attr(rb_cClient, "midi_in", 1, 1); + rb_define_attr(rb_cClient, "midi_out", 1, 1); + rb_require("concurrent"); + + /* TODO: fix as per the last comment here: https://stackoverflow.com/questions/6442920/how-do-i-extend-my-ruby-class-with-a-c-extension */ + + VALUE rb_cConcurrent = rb_const_get(rb_cObject, rb_intern("Concurrent")); + rb_cCachedThreadPool = rb_const_get(rb_cConcurrent, rb_intern("CachedThreadPool")); } diff --git a/ext/rcoremidi/rcoremidi.h b/ext/rcoremidi/rcoremidi.h index e053295..106dd9a 100644 --- a/ext/rcoremidi/rcoremidi.h +++ b/ext/rcoremidi/rcoremidi.h @@ -11,6 +11,11 @@ #include "midi_object.h" + +extern VALUE rb_cCachedThreadPool; + + + extern VALUE rb_cConectionManager; extern VALUE rb_cDevice; extern VALUE rb_cEntity; diff --git a/lib/rcoremidi.rb b/lib/rcoremidi.rb index dd0a2c4..dda97e9 100644 --- a/lib/rcoremidi.rb +++ b/lib/rcoremidi.rb @@ -1,5 +1,6 @@ require 'securerandom' require 'rcoremidi.bundle' +require 'rcoremidi/app_pathname' require 'rcoremidi/device' require 'rcoremidi/entity' require 'rcoremidi/source' diff --git a/lib/rcoremidi/application.rb b/lib/rcoremidi/application.rb index 9890e91..7aafe00 100644 --- a/lib/rcoremidi/application.rb +++ b/lib/rcoremidi/application.rb @@ -154,7 +154,6 @@ def client end def midi_in - pp connection @midi_in ||= MIDIObject.find_by_unique_id(connection.last['midi_in']) end diff --git a/lib/rcoremidi/client.rb b/lib/rcoremidi/client.rb index 4503491..c6c6f50 100644 --- a/lib/rcoremidi/client.rb +++ b/lib/rcoremidi/client.rb @@ -2,14 +2,11 @@ module RCoreMidi class Client # :nodoc: - attr_accessor :midi_in, :midi_out def on_tick(midi_beat_clock) - puts midi_beat_clock # return unless bar_start?(midi_beat_clock) - byebug - to_send = live.beats_for(current_tick) - ap to_send + to_send = live.beats_for(midi_beat_clock) + # ap [midi_beat_clock, to_send] send_packets(midi_out, to_send) end @@ -26,5 +23,9 @@ def create_live def connect! connect_to midi_in end + + def pool + @pool ||= Concurrent::CachedThreadPool.new + end end end diff --git a/lib/rcoremidi/duration_calculator.rb b/lib/rcoremidi/duration_calculator.rb index 76c27a9..99e2c66 100644 --- a/lib/rcoremidi/duration_calculator.rb +++ b/lib/rcoremidi/duration_calculator.rb @@ -15,7 +15,8 @@ def timestamps_for(note_index, resolution = 16) # A probabilities array of length 16 means # our current resolution is an 16th note - on = (mpt * 6 * note_index).round + # on = (mpt * 6 * note_index).round + on = 0 [on, (on + default_note_off_offset).round] end diff --git a/lib/rcoremidi/live.rb b/lib/rcoremidi/live.rb index 13048bf..00a5f32 100644 --- a/lib/rcoremidi/live.rb +++ b/lib/rcoremidi/live.rb @@ -9,21 +9,18 @@ class Live # :nodoc: PPQN = 24 def beats_for(current_tick) + puts "beats[#{current_tick}] = #{beats[current_tick]}" beats[current_tick] end def load(current_bar = 1) beats.each_with_index do |beat, i| Instrument.all.each do |instrument| - instrument_beats = instrument.bar(current_bar) - begin - beat << instrument_beats[i] unless instrument_beats[i].empty? - rescue - byebug - end + beats[i].concat(instrument_beats[i]) unless instrument_beats[i].empty? end end + ap beats end def beats diff --git a/rcoremidi.gemspec b/rcoremidi.gemspec index b509d4b..e8febb8 100644 --- a/rcoremidi.gemspec +++ b/rcoremidi.gemspec @@ -23,6 +23,7 @@ the osx CoreMidi Framework} s.add_dependency 'thor' s.add_dependency 'listen' + s.add_dependency 'concurrent-ruby' s.add_development_dependency 'rdoc' s.add_development_dependency 'rspec' s.add_development_dependency 'rspec-its' From 58bea1823a1ccd51d021ee668b4d1562f531b1e6 Mon Sep 17 00:00:00 2001 From: yann marquet Date: Wed, 18 Apr 2018 20:11:05 +0100 Subject: [PATCH 11/11] proof of concept working with thread pool --- ext/rcoremidi/client.c | 26 ++++++-------------------- ext/rcoremidi/rcoremidi.c | 2 -- ext/rcoremidi/rcoremidi.h | 2 -- lib/rcoremidi/client.rb | 6 +++--- 4 files changed, 9 insertions(+), 27 deletions(-) diff --git a/ext/rcoremidi/client.c b/ext/rcoremidi/client.c index f4c3a74..4ff7e5a 100644 --- a/ext/rcoremidi/client.c +++ b/ext/rcoremidi/client.c @@ -51,27 +51,11 @@ static void stop_waiting_for_callback_signal(void *w) pthread_mutex_unlock(&g_callback_mutex); } -static VALUE handle_callback(VALUE block_arg, VALUE data, int argc, VALUE* argv) { - - printf("Handle callback: args: %d\n", argc); - /* callback_t *cb = (callback_t*)callback; */ - /* RCoremidiNode *clientNode = (RCoremidiNode *)cb->data; */ - - /* rb_funcall(clientNode->rb_client_obj, rb_intern("on_tick"), 1, UINT2NUM(clientNode->transport->tick_count)); */ - - /* pthread_mutex_lock(&cb->mutex); */ - /* pthread_cond_signal(&g_callback_cond); */ - /* pthread_mutex_unlock(&cb->mutex); */ +static VALUE handle_callback(VALUE midi_clock, VALUE client, int argc, VALUE* argv) { + rb_funcall(client, rb_intern("on_tick"), 1, midi_clock); return Qnil; } -/* VALUE my_block(VALUE block_arg, VALUE data, int argc, VALUE* argv) */ -/* { */ -/* /\* block_arg will be the first yielded value *\/ */ -/* /\* data will be the last argument you passed to rb_block_call *\/ */ -/* /\* if multiple values are yielded, use argc/argv to access them *\/ */ -/* } */ - static VALUE boot_callback_event_thread(void * data) { callback_waiting_t waiting = { .callback = NULL, .abort = false @@ -84,7 +68,9 @@ static VALUE boot_callback_event_thread(void * data) { callback_t *cb = (callback_t*)waiting.callback; RCoremidiNode *clientNode = (RCoremidiNode *)cb->data; VALUE pool = rb_iv_get(clientNode->rb_client_obj, "@pool"); - rb_block_call(pool, rb_intern("post"), 0, NULL, handle_callback, Qnil); + VALUE *block_args = &clientNode->rb_client_obj; + VALUE midi_beat = UINT2NUM(clientNode->transport->tick_count); + rb_block_call(pool, rb_intern("post"), 1, &midi_beat, handle_callback, clientNode->rb_client_obj); } } return Qnil; @@ -432,7 +418,7 @@ VALUE client_init(VALUE self, VALUE name) cb_thread = rb_thread_create(boot_callback_event_thread, NULL); rb_funcall(cb_thread, rb_intern("abort_on_exception="), 1, Qtrue); rb_iv_set(self, "@cb_thread", cb_thread); - + rb_iv_set(self, "@midi_clock", UINT2NUM(0)); rb_iv_set(self, "@pool", rb_funcall(rb_cCachedThreadPool, new_intern, 0, Qnil)); return self; } diff --git a/ext/rcoremidi/rcoremidi.c b/ext/rcoremidi/rcoremidi.c index c177363..65a66ec 100644 --- a/ext/rcoremidi/rcoremidi.c +++ b/ext/rcoremidi/rcoremidi.c @@ -21,7 +21,6 @@ ID empty_intern; ID lock_intern; ID length_intern; ID pack_intern; -ID core_midi_cb_thread_intern; ID on_intern; ID off_intern; ID on_timestamp_intern; @@ -70,7 +69,6 @@ Init_rcoremidi() devices_intern = rb_intern("@@devices"); empty_intern = rb_intern("empty?"); length_intern = rb_intern("length"); - core_midi_cb_thread_intern = rb_intern("@@core_midi_cb_thread"); on_intern = rb_intern("on"); off_intern = rb_intern("off"); on_timestamp_intern = rb_intern("on_timestamp"); diff --git a/ext/rcoremidi/rcoremidi.h b/ext/rcoremidi/rcoremidi.h index 106dd9a..24a0c88 100644 --- a/ext/rcoremidi/rcoremidi.h +++ b/ext/rcoremidi/rcoremidi.h @@ -10,8 +10,6 @@ #include "client.h" #include "midi_object.h" - - extern VALUE rb_cCachedThreadPool; diff --git a/lib/rcoremidi/client.rb b/lib/rcoremidi/client.rb index c6c6f50..a66c911 100644 --- a/lib/rcoremidi/client.rb +++ b/lib/rcoremidi/client.rb @@ -3,10 +3,10 @@ module RCoreMidi class Client # :nodoc: - def on_tick(midi_beat_clock) + def on_tick(m) # return unless bar_start?(midi_beat_clock) - to_send = live.beats_for(midi_beat_clock) - # ap [midi_beat_clock, to_send] + to_send = live.beats_for(m) + ap "m: #{m}" send_packets(midi_out, to_send) end