Permalink
Browse files

Done 'recorder.rb'. Also polling + event_input were a mess

  • Loading branch information...
1 parent b06c6ec commit 14d9dd38866382727d4d19cc0cecc337a7b4c994 @EugeneBrazwick committed Mar 29, 2010
View
26 bin/rrecordmidi
@@ -1,4 +1,4 @@
-#!/usr/bin/ruby1.9.1 -w
+#!/usr/bin/ruby -w
=begin
* rrecordmidi.rb - record standard MIDI files from sequencer ports
*
@@ -29,10 +29,15 @@ IMPORTANT: never been tested on writing correct MIDI files!
# /* TODO: sequencer queue timer selection */
-require_relative 'rrts'
+require_relative '../lib/rrts/rrts'
include RRTS::Driver
+def tag msg = ''
+ # avoid puts for threading problems
+ STDERR.print "#{caller[0]} #{msg}\n"
+end
+
BUFFER_SIZE = 4088
# ARA: added. Probably a define in the original Makefile.
@@ -560,18 +565,21 @@ end
Signal.trap('INT') { @stop = true }
Signal.trap('TERM') { @stop = true }
-# FIXME. this is a busy loop. Why not just use blocking ???
npfds = @seq.poll_descriptors_count(POLLIN);
until @stop do
# trace do
- descriptors = @seq.poll_descriptors(npfds, POLLIN);
- revents = @seq.poll_descriptors_revents(descriptors)
+ descriptors = @seq.poll_descriptors(npfds, POLLIN);
+# tag "block for input"
+ descriptors.poll
+# tag "input pending"
# end
- more = true
- until @stop || !more
- (event, more = @seq.event_input) or break
+# more = true
+# until @stop || !more
+# tag "calling event_input"
+ event = @seq.event_input
+# tag "received event #{event.inspect}"
record_event event
- end
+# end
end
# trace
finish_tracks
View
36 bin/rrecordmidi++
@@ -1,4 +1,4 @@
-#!/usr/bin/ruby1.9.1 -w
+#!/usr/bin/ruby -w
# encoding: utf-8
=begin
@@ -30,7 +30,7 @@ ARA: copied src at 17 feb 2010
This is the Object Oriented version of rrecordmidi.rb.
=end
-require_relative './alsa_midi.so'
+require_relative '../lib/rrts/driver/alsa_midi.so'
include RRTS::Driver
@@ -170,7 +170,8 @@ class Metronome #:no-doc:
end # Metronome
def create_queue
- require_relative 'midiqueue'
+ require_relative '../lib/rrts/midiqueue'
+ require_relative '../lib/rrts/tempo'
@queue = MidiQueue.new @sequencer, 'rrecordmidi',
tempo: Tempo.new(@smpte_timing ? @frames : @beats,
smpte_timing: @smpte_timing,
@@ -190,7 +191,7 @@ end
def connect_ports
sp, mp = @source_ports.each, @myports.each
- loop { mp.next. sp.next }
+ loop { mp.next.connect_from sp.next }
end
# records a byte to be written to the .mid file
@@ -245,9 +246,9 @@ end
def record_event ev
# ignore events without proper timestamps
-# puts "ev.receiver_queue_id=#{ev.receiver_queue_id}, queue.id=#{@queue.id}"
+# puts "ev.receiver_queue=#{ev.receiver_queue}, queue.id=#{@queue.id}"
# puts "ev.time=#{ev.time.inspect}"
- return if ev.receiver_queue_id != @queue.id || !(Integer === ev.time)
+ return if ev.receiver_queue != @queue.id || !(Integer === ev.time)
# puts "handling #{ev.type}"
print '.'
# determine which track to record to
@@ -496,7 +497,7 @@ opts.on('-s', '--[no-]channel-split') { |val| @channel_split = val }
# trace
# open sequencer
-require_relative 'sequencer'
+require_relative '../lib/rrts/sequencer'
include RRTS
Sequencer.new('rrecordmidi') do |seq|
@sequencer = seq # export ....
@@ -562,20 +563,19 @@ Sequencer.new('rrecordmidi') do |seq|
@metronome.pattern 0
end
- Signal.trap('INT') { @stop = true }
- Signal.trap('TERM') { @stop = true }
-
# FIXME. this is a busy loop. Why not just use blocking ???
- npfds = @sequencer.poll_descriptors_count(POLLIN);
- until @stop do
+ npfds = @sequencer.poll_descriptors_count(POLLIN)
+ descriptors = @sequencer.poll_descriptors(npfds, POLLIN)
+ loop do
+ begin
# trace do
- descriptors = @sequencer.poll_descriptors(npfds, POLLIN);
- revents = @sequencer.poll_descriptors_revents(descriptors)
+ descriptors.poll # infinite block
+# revents = @sequencer.poll_descriptors_revents(descriptors)
# end
- more = true
- until @stop || !more
- (event, more = @sequencer.event_input) or break
- record_event event
+ record_event @sequencer.event_input
+ rescue Interrupt
+ tag "interrupt handled silently"
+ break
end
end
# trace
View
19 extsrc/arecordmidi.c
@@ -1,4 +1,5 @@
-/*
+/* gcc ../extsrc/arecordmidi.c -o arecordmidi -I /usr/include/alsa /usr/lib/libasound.so
+ *
* arecordmidi.c - record standard MIDI files from sequencer ports
*
* Copyright (c) 2004-2005 Clemens Ladisch <clemens@ladisch.de>
@@ -29,8 +30,10 @@
#include <getopt.h>
#include <sys/poll.h>
#include <alsa/asoundlib.h>
-#include "aconfig.h"
-#include "version.h"
+// #include "aconfig.h"
+// #include "version.h"
+
+#define SND_UTIL_VERSION_STR "1.0"
#define BUFFER_SIZE 4088
@@ -855,15 +858,15 @@ int main(int argc, char *argv[])
signal(SIGINT, sighandler);
signal(SIGTERM, sighandler);
- fprintf(stderr, "calling poll_descriptors_count\n");
+// fprintf(stderr, "calling poll_descriptors_count\n");
npfds = snd_seq_poll_descriptors_count(seq, POLLIN);
pfds = alloca(sizeof(*pfds) * npfds);
for (;;) {
- fprintf(stderr, "calling poll_descriptors\n");
+// fprintf(stderr, "calling poll_descriptors\n");
snd_seq_poll_descriptors(seq, pfds, npfds, POLLIN);
- fprintf(stderr, "calling poll\n");
- if (poll(pfds, npfds, -1) < 0)
- break;
+// fprintf(stderr, "calling poll\n");
+ if (poll(pfds, npfds, -1) < 0) /* -1 == block forever */
+ break;
do {
snd_seq_event_t *event;
err = snd_seq_event_input(seq, &event);
View
108 lib/rrts/driver/alsa_seq.cpp
@@ -106,7 +106,7 @@ wrap_snd_seq_client_id(VALUE v_seq)
return INT2NUM(r);
}
-/* nonblock([nonblock])
+/* nonblock([none])
Set nonblock mode.
Parameters:
@@ -295,47 +295,44 @@ i_event_input(void *ptr)
{
snd_seq_event_t *ev = 0;
snd_seq_t *seq = (snd_seq_t *)ptr;
- int r = snd_seq_event_input(seq, &ev);
+ const int r = snd_seq_event_input(seq, &ev);
// according to mailing lists, these must NOT be freed.
// And it can't even since event_free is deprecated
if (r < 0)
{
VALUE cls = alsaMidiError;
+ const char *msg = snd_strerror(r);
switch (r)
{
case -EAGAIN:
cls = rb_funcall(rb_mErrno, rb_intern("const_get"), 1, ID2SYM(rb_intern("EAGAIN")));
- r = -r;
case -ENOSPC:
+// fprintf(stderr, "blocking mode = %d\n", snd_seq_get
cls = rb_funcall(rb_mErrno, rb_intern("const_get"), 1, ID2SYM(rb_intern("ENOSPC")));
- r = -r;
+ msg = "no space for event, you probably called snd_seq_event_input too soon";
break;
}
- rb_raise(cls, "%s", snd_strerror(r));
- ev = 0;
+ rb_raise(cls, "%s", msg);
}
- // fprintf(stderr, __FILE__":%d:event_input -> %p\n", __LINE__, ev);
- return rb_ary_new3(2, Data_Wrap_Struct(alsaMidiEventClass, 0/*mark*/, 0/*free*/, ev),
- INT2BOOL(r > 0));
+ // Otherwise r is always 1; the alsa documentation is WRONG!!
+// fprintf(stderr, __FILE__":%d:event_input -> %p, rescode=%d\n", __LINE__, ev, r);
+ return Data_Wrap_Struct(alsaMidiEventClass, 0/*mark*/, 0/*free*/, ev);
}
-/* [AlsaMidiEvent_i, more] event_input
+/* call-seq:
+ event_input -> AlsaMidiEvent_i
retrieve an event from sequencer
Obtains an input event from sequencer.
This function firstly receives the event byte-stream data from sequencer as much as
possible at once. Then it retrieves the first event record and store the pointer on ev.
By calling this function sequentially, events are extracted from the input buffer.
If there is no input from sequencer, function falls into sleep in blocking mode until
-an event is received, or returns nil in non-blocking mode. Occasionally, it may raise
+an event is received, or raise EAGAIN in non-blocking mode. Occasionally, it may raise
the ENOSPC SystemError. This means that the input FIFO of sequencer overran,
and some events are lost. Once this error is returned, the input FIFO is cleared automatically.
-Function returns the event plus a boolean indicating more bytes remain in the input buffer.
-It may also raise the EAGAIN SystemError if in nonblocking mode.
-An application can determine from the returned value whether to call input once more or not,
-if there's more data it will probably(!) not block, even in blocking mode.
-
+Function returns the event. It may also raise the EAGAIN SystemError if in nonblocking mode.
*/
static VALUE
wrap_snd_seq_event_input(VALUE v_seq)
@@ -1636,14 +1633,16 @@ wrap_snd_seq_set_queue_timer(VALUE v_seq, VALUE v_qid, VALUE v_timer)
return v_seq;
}
-/* int poll_descriptors_count(eventmask)
+/* call-seq:
+ poll_descriptors_count(eventmask) -> int
Parameters:
- [eventmask] the poll events to be checked (POLLIN or POLLOUT or combination)
+ [eventmask] (int) the poll events to be checked (POLLIN or POLLOUT or combination)
Get the number of poll descriptors. The polling events to be checked can be
specified by the second argument.
- When both input and output are to be checked, pass POLLIN|POLLOUT
+ When both input and output are to be checked, pass POLLIN|POLLOUT.
+ There are also two constants in Sequencer: Sequencer::PollIn and Sequencer::PollOut.
*/
static VALUE
wrap_snd_seq_poll_descriptors_count(VALUE v_seq, VALUE v_pollflags)
@@ -1653,9 +1652,11 @@ wrap_snd_seq_poll_descriptors_count(VALUE v_seq, VALUE v_pollflags)
return INT2NUM(snd_seq_poll_descriptors_count(seq, NUM2INT(v_pollflags)));
}
-/* AlsaPollFds_i poll_descriptors([space, ]eventmask)
+/* call-seq:
+ poll_descriptors([[space, ]eventmask]) -> AlsaPollFds_i
Get poll descriptors. If space is omitted snd_seq_poll_descriptors_count is used for that.
+If eventmask is missing as well then PollIn is used.
Parameters:
[space] space in the poll descriptor array
@@ -1665,49 +1666,52 @@ Get poll descriptors assigned to the sequencer handle. Since a sequencer handle
you need to set which direction(s) is/are polled in events argument. When POLLIN bit is specified,
the incoming events to the ports are checked.
+These descriptors have a method poll() that serves as a wrapper around the poll() system call.
To check the returned poll-events, call #poll_descriptors_revents
instead of reading the pollfd structs directly.
-
-Alsa examples call fds.poll(timeout_msec) since revents has a fixed and/or unknown timeout.
-Whatever.
*/
static VALUE
wrap_snd_seq_poll_descriptors(int argc, VALUE *argv, VALUE v_seq)
{
VALUE v_fdcount, v_pollflags;
- rb_scan_args(argc, argv, "11", &v_fdcount, &v_pollflags);
+ rb_scan_args(argc, argv, "02", &v_fdcount, &v_pollflags);
snd_seq_t *seq;
Data_Get_Struct(v_seq, snd_seq_t, seq);
size_t space;
+ int pollflags;
if (NIL_P(v_pollflags))
{
v_pollflags = v_fdcount;
- space = snd_seq_poll_descriptors_count(seq, NUM2INT(v_pollflags));
+ pollflags = NIL_P(v_pollflags) ? POLLIN : NUM2INT(v_pollflags);
+ space = snd_seq_poll_descriptors_count(seq, pollflags);
}
else
+ {
space = NUM2UINT(v_fdcount);
+ pollflags = NUM2INT(v_pollflags);
+ }
// 2
struct pollfd * const fds = ALLOC_N(struct pollfd, space + sizeof(size_t));
- if (!fds) return INT2NUM(-ENOMEM);
// 3
*(size_t *)fds = space;
- /*const int fill = */ snd_seq_poll_descriptors(seq, (struct pollfd *)(((size_t *)fds) + 1), space, NUM2INT(v_pollflags));
+ /*const int fill = */
+ snd_seq_poll_descriptors(seq, (struct pollfd *)(((size_t *)fds) + 1), space, pollflags);
// return rb_ary_new3(2, INT2NUM(fill), v_room);
return Data_Wrap_Struct(alsaPollFdsClass, 0/*mark*/, free/*free*/, fds);
}
/* boolarray poll_descriptors_revents(pollfds)
-get returned events from poll descriptors
+get returned events from poll descriptors, after you called AlsaPollFds_i#poll().
Parameters:
pollfds AlsaPollFds_i, the poll descriptors
Returns boolean array or nil if there are no events.
-The resulintg array holds an entry per filedescriptor, in the same order, holding true if
+The resulting array holds an entry per filedescriptor, in the same order, holding true if
there was an event at that index. At least one of them must hold true.
-However, you cannot specify a poll timeout, and the alsa examples all use poll instead!!
+NOTE: this method does not poll() at all.
*/
static VALUE
wrap_snd_seq_poll_descriptors_revents(VALUE v_seq, VALUE v_fds)
@@ -1720,7 +1724,7 @@ wrap_snd_seq_poll_descriptors_revents(VALUE v_seq, VALUE v_fds)
unsigned short revents[nfds];
const int r = snd_seq_poll_descriptors_revents(seq, (struct pollfd *)(((size_t *)fds) + 1), nfds, revents);
if (r < 0)
- RAISE_MIDI_ERROR("polling descriptors", r);
+ RAISE_MIDI_ERROR("fetching revents", r);
bool located = false;
for (size_t i = 0; i < nfds && !located; i++)
located = revents[i];
@@ -1734,31 +1738,51 @@ wrap_snd_seq_poll_descriptors_revents(VALUE v_seq, VALUE v_fds)
return v_revents;
}
-/* boolarray poll(timeout_msec)
+struct wrapPolldata
+{
+ GCSafeValue v_descriptors;
+ GCSafeValue v_timeout_msec;
+};
-Wrapper around poll (not ppoll -- currently)
-Returns nil if no events where present
-Returns an array of booleans where the index matches the polldescriptors passed.
-At least one of them will be true.
-*/
static VALUE
-wrapPoll(VALUE v_descriptors, VALUE v_timeout_msec)
+i_wrapPoll(void *ptr)
{
+ const struct wrapPolldata *const data = (struct wrapPolldata *)ptr;
struct pollfd *fds;
- Data_Get_Struct(v_descriptors, struct pollfd, fds);
+ Data_Get_Struct(data->v_descriptors, struct pollfd, fds);
const size_t nfds = *(size_t *)fds;
fds = (struct pollfd *)(((size_t *)fds) + 1);
- const int r = poll(fds, nfds, NUM2UINT(v_timeout_msec));
+ const int r = poll(fds, nfds, NIL_P(data->v_timeout_msec) ? -1 : NUM2UINT(data->v_timeout_msec));
if (r < 0)
RAISE_MIDI_ERROR("polling descriptors", r);
- else if (r == 0)
+ if (r == 0)
return Qnil;
VALUE v_revents = rb_ary_new2(nfds);
for (size_t i = 0; i < nfds; i++)
rb_ary_store(v_revents, i, INT2BOOL(fds[i].revents));
return v_revents;
}
+/* boolarray poll([timeout_msec = -1])
+
+Wrapper around poll (not ppoll -- currently)
+Returns nil if no events where present
+Returns an array of booleans where the index matches the polldescriptors passed.
+At least one of them will be true.
+
+This method will block if +timeout_msec+ is larger than 0, regardless of the sequencers
+blocking mode (since we don't depend on AlsaSequencer_i).
+If timeout is -1 it will block indefinitely
+*/
+static VALUE
+wrapPoll(int argc, VALUE *argv, VALUE v_descriptors)
+{
+ VALUE v_timeout_msec;
+ rb_scan_args(argc, argv, "01", &v_timeout_msec);
+ struct wrapPolldata data = { v_descriptors, v_timeout_msec };
+ return rb_thread_blocking_region(i_wrapPoll, &data, RUBY_UBF_IO, 0);
+}
+
/* int output_buffer_size
Obtains the size in bytes of output buffer. This buffer is used to store decoded
@@ -2080,7 +2104,7 @@ void alsa_seq_init()
rb_define_method(alsaSequencerClass, "port_info", RUBY_METHOD_FUNC(wrap_snd_seq_get_port_info), 1);
rb_define_method(alsaSequencerClass, "any_port_info", RUBY_METHOD_FUNC(wrap_snd_seq_get_any_port_info), 2);
rb_define_method(alsaSequencerClass, "set_port_info", RUBY_METHOD_FUNC(wrap_snd_seq_set_port_info), 2);
- rb_define_method(alsaPollFdsClass, "poll", RUBY_METHOD_FUNC(wrapPoll), 1);
+ rb_define_method(alsaPollFdsClass, "poll", RUBY_METHOD_FUNC(wrapPoll), -1);
rb_define_method(alsaSequencerClass, "remove_events", RUBY_METHOD_FUNC(wrap_snd_seq_remove_events), -1);
rb_define_method(alsaSequencerClass, "client_pool", RUBY_METHOD_FUNC(wrap_snd_seq_get_client_pool), -1);
rb_define_method(alsaSequencerClass, "client_pool=", RUBY_METHOD_FUNC(wrap_snd_seq_set_client_pool), 1);
View
12 lib/rrts/midievent.rb
@@ -339,9 +339,17 @@ def priority
0
end
- # this ruins track... But we may be able to recover from it...
+ # this ruins the event... But we may be able to recover from it...
def to_yaml *args
- @track = @track.key if @track.respond_to?(:key)
+ if instance_variable_defined?(:@track) # don't wake the sleeping dog
+ @track = @track.key if @track.respond_to?(:key)
+ end
+ if instance_variable_defined?(:@dest)
+ @dest = @dest.address if @dest.respond_to?(:address)
+ end
+ if instance_variable_defined?(:@source)
+ @source = @source.address if @source.respond_to?(:address)
+ end
super
end
View
4 lib/rrts/midiport.rb
@@ -277,8 +277,8 @@ def connect_from port
@seq_handle.connect_from self, port
end
- alias :>> :connect_to
- alias :<< :connect_from
+# alias :>> :connect_to
+# alias :<< :connect_from Ambiguous with sending events.
# bool capability?(symbolarray)
# better way to query caps. Pass the symbols to query
View
5 lib/rrts/midiqueue.rb
@@ -58,11 +58,12 @@ def initialize sequencer, name, params = nil
end
protected
- # DO NOT USE
- attr :id
public
+ # DO NOT USE. Used by rrecordmidi++ to identify a queue.
+ attr :id
+
# free the queue. If it is still running it is stopped first
def free
return unless @id
View
52 lib/rrts/node/defoptions.rb
@@ -16,6 +16,12 @@ def initialize arguments = ARGV
@spam = false
@blockingmode = true
@write_ahead = 3
+ @smpte_timing = false
+ @beats = 120 # quarters per minute
+ @frames = 0
+ @channel_split = true
+ @ticks = nil # default 384 ticks per quarter or 40 for smpte_timing
+
Sequencer.new do |seq|
opts.banner = "Usage: #$PROGRAM_NAME [options]"
opts.on('-h', '--help', 'this help') { puts opts.to_s; exit 1 }
@@ -41,9 +47,32 @@ def initialize arguments = ARGV
end
opts.on('-i', '--input=VAL') { |arg| @input = arg }
opts.on('-o', '--output=VAL') { |arg| @output = arg }
- opts.on('--nonblocking') { @blockingmode = false }
+ opts.on('--[no-]blocking') { |arg| @blockingmode = arg }
opts.on('--write_ahead=VAL', 'in seconds', Integer) { |arg| @write_ahead = arg }
+ opts.on('-s', '--[no-]channel-split') { |arg| @channel_split = arg }
+
+ opts.on('-b', '--bpm=VAL', '--beats=VAL', Integer,
+ 'tempo in beats per minute') do |bpm|
+ raise OptionParser::InvalidArgument.new("Invalid tempo #{bpm}") unless (4..6000) === bpm
+ @beats = bpm
+ @smpte_timing = false
+ end
+
+ opts.on('-f', '--fps=VAL', '--frames=VAL', Integer, [24, 25, 39, 30],
+ 'use frames per second') do |fps|
+ @frames = fps
+ @smpte_timing = true
+ end
+
+ opts.on('-t', '--ticks=VAL', Integer, 'use ticks per beat or frame') do |ticks|
+ raise OptionParser::InvalidArgument.new('Invalid number of ticks') unless (1..0x7fff) === ticks
+ @ticks = ticks
+ end
+
opts.parse arguments
+ @ticks = @smpte_timing ? 40 : 384 unless @ticks
+ @ticks = 255 if @smpte_timing && @ticks > 255
+
end # close Sequencer
end # initialize
@@ -58,7 +87,21 @@ def escape_shell_single_word token
public
- attr :input, :output
+ attr :input, :output #, :ticks, :beats, :frames, :write_ahead, :clientname
+
+=begin
+ def smpte_timing?
+ @smpte_timing
+ end
+
+ def blocking?
+ @blockingmode
+ end
+
+ def spam?
+ @spam
+ end
+=end
# create an input node belongning to input
def input_node
@@ -72,7 +115,10 @@ def input_node
require_relative 'midifilereader'
MidiFileReader.new(@input)
else
- todo 'read from alsaport'
+ require_relative 'recorder'
+ Recorder.new(@input, clientname: @clientname, blockingmode: @blockingmode,
+ smpte_timing: @smpte_timing, frames: @frames, beats: @beats,
+ ticks: @ticks, channel_split: @channel_split)
end
end
View
4 lib/rrts/node/player.rb
@@ -4,6 +4,7 @@ module RRTS
module Node
require_relative 'node'
+ require_relative '../rrts'
# this class is related to MidiIOWriter and YamlIOWriter.
# this time, it writes to a sequencer
@@ -34,8 +35,7 @@ class Player < Base
def initialize dest_port_specifier, input_node = nil, options = {}
(options, input_node = input_node, nil) if Hash === input_node
# candidate option:
- # [ threaded ] Create the sequencer in a thread. Doing this will not prevent ruby from
- # being locked up. Do not use blockingmode.
+ # [ threaded ] Create the sequencer in a thread.
@dest_port_specifier = dest_port_specifier
@name = 'rplayer' # name for the client
@end_delay = nil
View
102 lib/rrts/node/recorder.rb
@@ -0,0 +1,102 @@
+#!/usr/bin/ruby -w
+
+module RRTS
+ module Node
+
+ require_relative 'node'
+ require_relative '../rrts' # for RRTSError
+
+ # this class is related to MidiIOReader and YamlIOReader.
+ # But now we read from one port (or several).
+ class Recorder < EventsNode
+
+ private
+
+=begin rdoc
+Create a new recorder. Parameters
+src_port_specifier:: comma separated list of port specifiers. See Sequencer::parse_address
+options:: a hash with the following names options:
+ smpte_timing:: bool, set with syncing with movies etc.
+ ticks:: ticks per pulse, default 384, or 40 if smpte_timing is set
+ beats:: beats per minute, default 120
+ frames:: frames per second, must be given
+ blockingmode:: default true
+ clientname:: default 'rrecorder'
+ client_name:: alias
+=end
+ def initialize src_port_specifier, options = {}
+ @src_port_specifiers = src_port_specifier.split(',')
+ @smpte_timing = false
+ @ticks = @frames = nil
+ @beats = 120
+ @client_name = 'rrecorder'
+ @blockingmode = true
+ for k, v in options
+ case k
+ when :smpte_timing then @smpte_timing = v
+ when :ticks then @ticks = v
+ when :blockingmode then @blockingmode = v
+ when :beats then @beats = v
+ when :frames then @frames = v
+ when :clientname, :client_name then @client_name = v
+ else raise RRTSError.new("illegal option '#{k}' for Recorder")
+ end
+ end
+ @ticks = @smpte_timing ? 40 : 384 unless @ticks
+ @ticks = 255 if @smpte_timing && @ticks > 255
+ end
+
+ public
+
+ # enumerate the events
+ def each &block
+ return to_enum unless block
+ require_relative '../sequencer'
+ require_relative '../midiport'
+ Sequencer.new(@client_name, blockingmode: @blockingmode) do |seq|
+# client = seq.client
+ queue = seq.create_queue(@client_name + '_q', smpte_timing: @smpte_timing,
+ frames: @frames, beats: @beats, ticks: @ticks)
+ port_params = { write: true, subs_write: true, midi_generic: true, application: true,
+ midi_channels: 16, timestamping: true, timestamp_queue: queue,
+ port_specified: false
+ }
+ ports = []
+ source_ports = @src_port_specifiers.map { |port_name| seq.parse_address port_name }
+ source_ports.each_with_index do |src, i|
+ ports << (port = MidiPort.new(seq, @client_name + ('_p%02d') % (i + 1), port_params))
+ port.connect_from src
+ end
+ queue.start
+ seq.flush
+ descriptors = seq.poll_descriptors
+ loop do
+ begin
+ descriptors.poll
+ event = seq.event_input
+ case event
+ when ClockEvent, TickEvent # do nothing
+ else
+ yield event
+ end
+ rescue Errno::EAGAIN
+ sleep(seq.polltime)
+ rescue Interrupt
+ # silent break
+ break
+ end
+ end # loop
+ end # close Sequencer
+ end # def each
+ end # class Recorder
+
+ end # module Node
+end # module RRTS
+
+if __FILE__ == $0
+ include RRTS
+ include Node
+ input = Recorder.new('20:0')
+ require_relative 'yamlwriter'
+ YamlPipeWriter.new(STDOUT, input)
+end
View
2 lib/rrts/node/yamlwriter.rb
@@ -39,7 +39,7 @@ def initialize filename, node = nil
class YamlPipeWriter < YamlIOWriter
private
- def initialize io = STDOUT
+ def initialize io = STDOUT, node = nil
case io
when String, Array
io = IO.popen(io, 'w')
View
57 lib/rrts/sequencer.rb
@@ -61,21 +61,21 @@ class Sequencer
PollIn = POLLIN
# for the poll methods
PollOut = POLLOUT
- POLLTIME = 0.01 # 10 ms
+# POLLTIME = 0.01 # 10 ms
private
=begin rdoc
parameters:
- [client_name] name of the instantiated client, if nil no client will be instantiated
- [params] hash of optional parameters:
- [:name] default 'default'. Do not alter.
- [:openmode] default Duplex
- [:map_ports] default true if client_name yields true
- [:blockingmode] default Blocking
- [:dump_notes] if true dump to stderr and do NOT play them!! Only works with HACKED cpp
- backend
- [:polltime] timeout in seconds for sleep, for nonblockingmode, default is 0.01
- If left nil methods will currently fail.
- [block] encapsulation for automatic close. Works like IO::open.
+ client_name:: name of the instantiated client, if nil no client will be instantiated
+ params:: hash of optional parameters:
+ name:: default 'default'. Do not alter.
+ openmode:: default Duplex
+ map_ports:: default true if client_name is given
+ blockingmode:: default Blocking
+ dump_notes:: if true dump to stderr and do NOT play them!! Only works with HACKED cpp
+ backend
+ polltime:: timeout in seconds for sleep, for nonblockingmode, default is 0.01
+ If left nil methods will currently fail.
+ block:: encapsulation for automatic close. Works like IO::open.
=end
def initialize client_name = nil, params = nil, &block
@client = @handle = nil
@@ -215,36 +215,25 @@ def client_name= arg
SND_SEQ_EVENT_USR_VAR4=>VarUserEvent
}
-# Returns a tuple MidiEvent + boolean, or nil
+# Returns a MidiEvent
#
# Obtains a MidiEvent from the sequencer.
# This function firstly receives the event byte-stream data from sequencer as much as possible at once.
# Then it retrieves the first event record.
# By calling this function sequentially, events are extracted from the input buffer.
# If there is no input from sequencer, function falls into sleep in blocking mode until
# an event is received,
-# or returns nil in non-blocking mode. Occasionally, it may raise ENOSPC error. This means
-# that the input
-# FIFO of sequencer overran, and some events are lost.
-# Once this error is returned, the input FIFO is cleared automatically.
-#
-# Function returns the event plus a boolean indicating more bytes remain in the input buffer
-# Application can determine from the returned value whether to call input once more or not,
-# if there's more data it will probably(!) not block, even in blocking mode.
+# or it raises Errno::EAGAIN in non-blocking mode.
#
-#Example:
-# remains = 1
-# while remains > 0
-# (ev, remains = @sequencer.event_input) or break
-# case ev
-# ..
-# end
-# end
+# Occasionally, it may raise an Errno::ENOSPC error. This means
+# that the input FIFO of sequencer overran, and some events are lost.
+# Once this error is returned, the input FIFO is cleared automatically.
#
def event_input
- (ev, more = @handle.event_input) or return nil
+# tag "Calling event_input"
+ ev = @handle.event_input
+# tag "received event #{ev.typeid}"
# typeid = ev.typeid
- klass = Klassmap[ev.type] || MidiEvent
# puts "#{File.basename(__FILE__)}:#{__LINE__}:typeid=#{typeid},vel=#{ev_i.velocity},NOTEON=#{SND_SEQ_EVENT_NOTEON}"
# LET's fix things later.
# if typeid == SND_SEQ_EVENT_NOTEON && ev.velocity == 0
@@ -256,7 +245,7 @@ def event_input
What is required is based solely on the typeid
=end
# puts "Instantiating #{klass}, since ev.type=#{ev.type.inspect}"
- return klass.new(self, ev), more
+ (Klassmap[ev.type] || MidiEvent).new(self, ev)
end
def_delegators :@handle, :poll_descriptors, :poll_descriptors_count, :poll_descriptors_revents,
@@ -287,7 +276,7 @@ def drain_output
begin
return @handle.drain_output
rescue Errno::EAGAIN
- sleep(POLLTIME)
+ sleep(@polltime)
end
end
# loop do
@@ -454,6 +443,8 @@ def client name = nil
attr :client_id
# returns a hash, indexed by queuename
attr :queues
+ # float, polltime in seconds
+ attr :polltime
end # Sequencer
end # RRTS

0 comments on commit 14d9dd3

Please sign in to comment.