#include #include #include #include #include #include #include #include #include "evoral/midi_events.h" #include "bb.h" #include "gui.h" using std::cerr; using std::endl; static bool second_simultaneous_midi_byte_is_first (uint8_t a, uint8_t b) { bool b_first = false; /* two events at identical times. we need to determine the order in which they should occur. the rule is: Controller messages Program Change Note Off Note On Note Pressure Channel Pressure Pitch Bend */ if ((a) >= 0xf0 || (b) >= 0xf0 || ((a & 0xf) != (b & 0xf))) { /* if either message is not a channel message, or if the channels are * different, we don't care about the type. */ b_first = true; } else { switch (b & 0xf0) { case MIDI_CMD_CONTROL: b_first = true; break; case MIDI_CMD_PGM_CHANGE: switch (a & 0xf0) { case MIDI_CMD_CONTROL: break; case MIDI_CMD_PGM_CHANGE: case MIDI_CMD_NOTE_OFF: case MIDI_CMD_NOTE_ON: case MIDI_CMD_NOTE_PRESSURE: case MIDI_CMD_CHANNEL_PRESSURE: case MIDI_CMD_BENDER: b_first = true; } break; case MIDI_CMD_NOTE_OFF: switch (a & 0xf0) { case MIDI_CMD_CONTROL: case MIDI_CMD_PGM_CHANGE: break; case MIDI_CMD_NOTE_OFF: case MIDI_CMD_NOTE_ON: case MIDI_CMD_NOTE_PRESSURE: case MIDI_CMD_CHANNEL_PRESSURE: case MIDI_CMD_BENDER: b_first = true; } break; case MIDI_CMD_NOTE_ON: switch (a & 0xf0) { case MIDI_CMD_CONTROL: case MIDI_CMD_PGM_CHANGE: case MIDI_CMD_NOTE_OFF: break; case MIDI_CMD_NOTE_ON: case MIDI_CMD_NOTE_PRESSURE: case MIDI_CMD_CHANNEL_PRESSURE: case MIDI_CMD_BENDER: b_first = true; } break; case MIDI_CMD_NOTE_PRESSURE: switch (a & 0xf0) { case MIDI_CMD_CONTROL: case MIDI_CMD_PGM_CHANGE: case MIDI_CMD_NOTE_OFF: case MIDI_CMD_NOTE_ON: break; case MIDI_CMD_NOTE_PRESSURE: case MIDI_CMD_CHANNEL_PRESSURE: case MIDI_CMD_BENDER: b_first = true; } break; case MIDI_CMD_CHANNEL_PRESSURE: switch (a & 0xf0) { case MIDI_CMD_CONTROL: case MIDI_CMD_PGM_CHANGE: case MIDI_CMD_NOTE_OFF: case MIDI_CMD_NOTE_ON: case MIDI_CMD_NOTE_PRESSURE: break; case MIDI_CMD_CHANNEL_PRESSURE: case MIDI_CMD_BENDER: b_first = true; } break; case MIDI_CMD_BENDER: switch (a & 0xf0) { case MIDI_CMD_CONTROL: case MIDI_CMD_PGM_CHANGE: case MIDI_CMD_NOTE_OFF: case MIDI_CMD_NOTE_ON: case MIDI_CMD_NOTE_PRESSURE: case MIDI_CMD_CHANNEL_PRESSURE: break; case MIDI_CMD_BENDER: b_first = true; } break; } } return b_first; } BeatBox::BeatBox (int sr) : _start_requested (false) , _running (false) , _measures (2) , _tempo (120) , _meter_beats (4) , _meter_beat_type (4) , _input (0) , _output (0) , superclock_cnt (0) , last_start (0) , _sample_rate (sr) , whole_note_superclocks (0) , beat_superclocks (0) , measure_superclocks (0) , _quantize_divisor (4) , clear_pending (false) { for (uint32_t n = 0; n < 1024; ++n) { event_pool.push_back (new Event()); } } BeatBox::~BeatBox () { } int BeatBox::register_ports (jack_client_t* jack) { if ((_input = jack_port_register (jack, "midi-in", JACK_DEFAULT_MIDI_TYPE, JackPortIsInput, 0)) == 0) { cerr << "no input port\n"; return -1; } if ((_output = jack_port_register (jack, "midi-out", JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput, 0)) == 0) { cerr << "no output port\n"; jack_port_unregister (jack, _input); return -1; } return 0; } void BeatBox::start () { /* compute tempo, beat steps etc. */ /* superclocks_per_minute = superclock_ticks_per_second * 60; beats_per_minute = _tempo; whole_notes_per_minute = beats_per_minute / _meter_beat_type; */ whole_note_superclocks = (superclock_ticks_per_second * 60) / (_tempo / _meter_beat_type); cerr << "there are " << _tempo / _meter_beat_type << " whole notes per minute, which is " << superclock_ticks_per_second * 60 << " sct, so wns = " << whole_note_superclocks << endl; beat_superclocks = whole_note_superclocks / _meter_beat_type; measure_superclocks = beat_superclocks * _meter_beats; /* we can start */ _start_requested = true; } void BeatBox::stop () { _start_requested = false; } int BeatBox::process (int nsamples) { if (!_running) { if (_start_requested) { _running = true; last_start = superclock_cnt; } } else { if (!_start_requested) { _running = false; } } superclock_t superclocks = samples_to_superclock (nsamples, _sample_rate); if (!_running) { superclock_cnt += superclocks; return 0; } superclock_t process_start = superclock_cnt - last_start; superclock_t process_end = process_start + superclocks; const superclock_t loop_length = _measures * measure_superclocks; const superclock_t orig_superclocks = superclocks; process_start %= loop_length; process_end %= loop_length; bool two_pass_required; superclock_t offset = 0; if (process_end < process_start) { two_pass_required = true; process_end = loop_length; superclocks = process_end - process_start; } else { two_pass_required = false; } unsigned char* buffer; void* out_buf; void* in_buf; jack_midi_event_t in_event; jack_nframes_t event_index; jack_nframes_t event_count; /* do this on the first pass only */ out_buf = jack_port_get_buffer (_output, nsamples); jack_midi_clear_buffer (out_buf); second_pass: /* Output */ if (clear_pending) { for (Events::iterator ee = _current_events.begin(); ee != _current_events.end(); ++ee) { event_pool.push_back (*ee); } _current_events.clear (); clear_pending = false; } for (Events::iterator ee = _current_events.begin(); ee != _current_events.end(); ++ee) { Event* e = (*ee); if (e->size && (e->time >= process_start && e->time < process_end)) { if ((buffer = jack_midi_event_reserve (out_buf, superclock_to_samples (offset + e->time - process_start, _sample_rate), e->size)) != 0) { memcpy (buffer, e->buf, e->size); } else { cerr << "Could not reserve space for output event @ " << e << " of size " << e->size << " @ " << offset + e->time - process_start << " (samples: " << superclock_to_samples (offset + e->time - process_start, _sample_rate) << ") offset is " << offset << ")\n"; } } if (e->time >= process_end) { break; } } /* input */ in_buf = jack_port_get_buffer (_input, nsamples); event_index = 0; while (jack_midi_event_get (&in_event, in_buf, event_index++) == 0) { superclock_t event_time = superclock_cnt + samples_to_superclock (in_event.time, _sample_rate); superclock_t elapsed_time = event_time - last_start; superclock_t in_loop_time = elapsed_time % loop_length; superclock_t quantized_time; if (_quantize_divisor != 0) { const superclock_t time_per_beat = whole_note_superclocks / _quantize_divisor; quantized_time = (in_loop_time / time_per_beat) * time_per_beat; } else { quantized_time = elapsed_time; } if (in_event.size > 24) { cerr << "Ignored large MIDI event\n"; continue; } if (event_pool.empty()) { cerr << "No more events, grow pool\n"; continue; } Event* e = event_pool.back(); event_pool.pop_back (); e->time = quantized_time; e->size = in_event.size; memcpy (e->buf, in_event.buffer, in_event.size); _current_events.insert (e); } superclock_cnt += superclocks; if (two_pass_required) { offset = superclocks; superclocks = orig_superclocks - superclocks; process_start = 0; process_end = superclocks; two_pass_required = false; cerr << "2nd Pass for " << superclocks << " offset = " << offset << endl; goto second_pass; } return 0; } void BeatBox::set_quantize (int divisor) { _quantize_divisor = divisor; } void BeatBox::clear () { clear_pending = true; } bool BeatBox::EventComparator::operator() (Event const * a, Event const *b) const { if (a->time == b->time) { if (a->buf[0] == b->buf[0]) { return a < b; } return !second_simultaneous_midi_byte_is_first (a->buf[0], b->buf[0]); } return a->time < b->time; } static int process (jack_nframes_t nsamples, void* arg) { BeatBox* bbox = static_cast (arg); return bbox->process (nsamples); } int main (int argc, char* argv[]) { jack_client_t* jack; const char *server_name = NULL; jack_options_t options = JackNullOption; jack_status_t status; if ((jack = jack_client_open ("beatbox", options, &status, server_name)) == 0) { cerr << "Could not connect to JACK\n"; return -1; } BeatBox* bbox = new BeatBox (jack_get_sample_rate (jack)); BBGUI* gui = new BBGUI (&argc, &argv, jack, bbox); bbox->register_ports (jack); jack_set_process_callback (jack, process, bbox); jack_activate (jack); bbox->start (); gui->run (); }