More robust automatic note-off abilities.
Pressing pause, stop, and editing patterns while transport is running causes playing notes to stop. Instead of using 'all notes off' controller change messages, the sequencer keeps track of playing notes and does a fast search to see which ones to cancel. This reduces midi bus traffic considerably. There is a potential race condition because both the audio thread (while sequencer is playing) and the GUI (when changes are made) manipulate the bit field used to track notes that are on. A possible error is when the user presses a control or edits a pattern just as a note should be ending. By the powers of multithreading, two note offs may be generated when there should only be one. I am considering this rare and harmless enough at this time to ignore it. Doing it correctly would require a better serialized, lock-free command queue, and would solve a few other race conditions. Maybe some other time.
This commit is contained in:
parent
30673a375d
commit
383ca5d308
21
src/jack.cpp
21
src/jack.cpp
|
@ -70,6 +70,7 @@ static int loop_start = 0;
|
|||
static int loop_end = TICKS_PER_BEAT*4;
|
||||
static int passthru = 1;
|
||||
static int rec_port = 0;
|
||||
|
||||
static int trackinit = 1;
|
||||
|
||||
static int init_chans = 1;
|
||||
|
@ -223,6 +224,7 @@ static int process(jack_nframes_t nframes, void* arg){
|
|||
for(int i=0; i<n; i++){
|
||||
md1[i]=buf[i];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@ -549,17 +551,14 @@ void midi_track_off(int track){
|
|||
int chan = tracks[track]->chan;
|
||||
int port = tracks[track]->port;
|
||||
|
||||
midi_channel_off(chan,port);
|
||||
}
|
||||
|
||||
void midi_channel_off(int chan, int port){
|
||||
char buf[3] = {0xB0,123,0};
|
||||
buf[0] = 0xB0 | chan;
|
||||
send_midi(buf,3,port);
|
||||
|
||||
buf[0] = 0xB0 | chan;
|
||||
buf[1] = 120;
|
||||
send_midi(buf,3,port);
|
||||
OnBitArray* onbits = &(tracks[track]->onbits);
|
||||
onbits->search_init();
|
||||
int note = onbits->search_next();
|
||||
while(note >= 0){
|
||||
midi_note_off(note, chan, port);
|
||||
note = onbits->search_next();
|
||||
}
|
||||
onbits->clear();
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -27,6 +27,7 @@
|
|||
#include <fltk/Button.h>
|
||||
#include <fltk/Input.h>
|
||||
#include <fltk/ValueInput.h>
|
||||
#include <fltk/TextDisplay.h>
|
||||
|
||||
|
||||
#include "ui.h"
|
||||
|
|
|
@ -339,6 +339,7 @@ int PianoRoll::handle(int event){
|
|||
move_flag = 0;
|
||||
|
||||
midi_track_off(cur_seqpat->track);
|
||||
|
||||
ui->keyboard->release_note(last_note,0);
|
||||
ui->keyboard->release_note(move_norig+move_noffset,0);
|
||||
ui->keyboard->redraw();
|
||||
|
@ -352,7 +353,9 @@ int PianoRoll::handle(int event){
|
|||
if(delete_flag && over_n){
|
||||
if(over_n->selected){
|
||||
apply_delete();
|
||||
|
||||
midi_track_off(cur_seqpat->track);
|
||||
|
||||
ui->event_edit->redraw();
|
||||
}
|
||||
}
|
||||
|
|
100
src/seq.cpp
100
src/seq.cpp
|
@ -25,9 +25,9 @@
|
|||
#include <list>
|
||||
#include <vector>
|
||||
|
||||
#include <fltk/TextDisplay.h>
|
||||
#include "seq.h"
|
||||
|
||||
#include <fltk/TextDisplay.h>
|
||||
#include "util.h"
|
||||
|
||||
#include "backend.h"
|
||||
|
@ -106,7 +106,7 @@ int play_seq(int cur_tick){
|
|||
p = s->p;
|
||||
e = s->skip;
|
||||
|
||||
if(!e){ goto switchpatseq; }//no more events in this seqpat
|
||||
if(!e){ goto switchblock; }//no more events in this seqpat
|
||||
|
||||
again:
|
||||
|
||||
|
@ -122,17 +122,21 @@ again:
|
|||
e = e->next;
|
||||
s->skip = e;
|
||||
if(!e){//no more events in this seqpat
|
||||
goto switchpatseq;
|
||||
goto switchblock;
|
||||
}
|
||||
}
|
||||
else if(e->tick > s->dur){//went past the end
|
||||
goto switchpatseq;
|
||||
goto switchblock;
|
||||
}
|
||||
else{//no more events on this track right now
|
||||
continue;
|
||||
}
|
||||
goto again;//try to play next event
|
||||
switchpatseq:
|
||||
|
||||
switchblock:
|
||||
|
||||
//TODO auto off all playing notes on track because end of block
|
||||
|
||||
s = s->next;
|
||||
|
||||
tracks[i]->skip = s;
|
||||
|
@ -151,7 +155,24 @@ switchpatseq:
|
|||
|
||||
std::list<total_event>::iterator i = dispatch_queue.begin();
|
||||
while(i != dispatch_queue.end()){
|
||||
OnBitArray* oba = &(tracks[i->t]->onbits);
|
||||
if(i->e->type == MIDI_NOTE_ON){
|
||||
if(!oba->get(i->e->value1)){
|
||||
tracks[i->t]->onbits.set(i->e->value1, 1);
|
||||
}
|
||||
else{
|
||||
mevent eoff = i->e;
|
||||
eoff.type = MIDI_NOTE_OFF;
|
||||
eoff.value2 = 0;
|
||||
dispatch_event(&eoff, i->t, i->s->tick);
|
||||
}
|
||||
}
|
||||
else if(i->e->type == MIDI_NOTE_OFF){
|
||||
tracks[i->t]->onbits.set(i->e->value1, 0);
|
||||
}
|
||||
|
||||
dispatch_event(i->e, i->t, i->s->tick);
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
|
@ -817,8 +838,6 @@ void pattern::fixdur(){
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
//used when the sequence is changed in such a way
|
||||
//that the sequencer state needs to be updated
|
||||
void track::restate(){
|
||||
|
@ -957,7 +976,7 @@ void seqpat::autocomplete(){
|
|||
|
||||
int chan = tracks[track]->chan;
|
||||
int port = tracks[track]->port;
|
||||
|
||||
printf("use of deprecated function seqpat::autocomplete\n");
|
||||
while(e){
|
||||
if(e->type == MIDI_NOTE_ON && e->tick < pos){
|
||||
eoff = find_off(e);
|
||||
|
@ -1164,3 +1183,68 @@ void undo_reset(){
|
|||
undo_nptr = undo_number.begin();
|
||||
undo_ptr = undo_stack.begin();
|
||||
}
|
||||
|
||||
|
||||
OnBitArray::OnBitArray(){
|
||||
for(int i=0; i<16; i++){
|
||||
bytes[i] = 0;
|
||||
}
|
||||
spos = 0;
|
||||
jpos = 0;
|
||||
}
|
||||
|
||||
int OnBitArray::get(int note){
|
||||
//note/8 = note >> 3
|
||||
//note%8 = note & 0x7
|
||||
return bytes[note >> 3] & (1 << (note & 0x7));
|
||||
}
|
||||
|
||||
void OnBitArray::set(int note, int value){
|
||||
//note/8 = note >> 3
|
||||
//note%8 = note & 0x3
|
||||
|
||||
if(value){
|
||||
bytes[note >> 3] |= (1 << (note & 0x7));
|
||||
}
|
||||
else{
|
||||
bytes[note >> 3] &= ~(1 << (note & 0x7));
|
||||
}
|
||||
}
|
||||
|
||||
void OnBitArray::search_init(){
|
||||
spos = 0;
|
||||
jpos = 0;
|
||||
}
|
||||
|
||||
int OnBitArray::search_next(){
|
||||
unsigned char byte;
|
||||
for(int i=spos; i<16; i++){
|
||||
byte = bytes[i];
|
||||
|
||||
if(byte){
|
||||
for(int j=jpos; j<8; j++){
|
||||
if(byte & (1<<j)){
|
||||
if(j==7){
|
||||
spos = i+1;
|
||||
jpos = 0;
|
||||
}
|
||||
else{
|
||||
spos = i;
|
||||
jpos = j+1;
|
||||
}
|
||||
int note = i*8 + j;
|
||||
//bytes[i] &= ~(1<<j);
|
||||
return note;
|
||||
}
|
||||
}
|
||||
jpos = 0;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void OnBitArray::clear(){
|
||||
for(int i=0; i<16; i++){
|
||||
bytes[i] = 0;
|
||||
}
|
||||
}
|
23
src/seq.h
23
src/seq.h
|
@ -33,6 +33,25 @@
|
|||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
|
||||
class OnBitArray {
|
||||
unsigned char bytes[16];
|
||||
int spos;
|
||||
int jpos;
|
||||
|
||||
public:
|
||||
|
||||
int get(int note);
|
||||
void set(int note, int value);
|
||||
|
||||
void search_init();
|
||||
int search_next();
|
||||
|
||||
void clear();
|
||||
|
||||
OnBitArray();
|
||||
};
|
||||
|
||||
struct mevent {
|
||||
|
||||
public:
|
||||
|
@ -279,6 +298,8 @@ struct track {
|
|||
seqpat* head;
|
||||
seqpat* skip;
|
||||
|
||||
OnBitArray onbits;
|
||||
|
||||
int modified;
|
||||
|
||||
void restate();
|
||||
|
@ -738,6 +759,8 @@ int set_seq_pos(int new_tick);
|
|||
void set_rec_track(int t);
|
||||
int get_rec_track();
|
||||
|
||||
void tracks_auto_off();
|
||||
|
||||
int set_default_hsv_value(float v);
|
||||
|
||||
void set_undo(Command* c);
|
||||
|
|
|
@ -46,18 +46,21 @@ void portcb(fltk::Widget* w, long i){
|
|||
//fltk::ValueInput* o = (fltk::ValueInput*)w;
|
||||
Gauge* o = (Gauge*)w;
|
||||
track* t = tracks[i];
|
||||
int old_port = t->port;
|
||||
t->port = (int)o->value;
|
||||
midi_channel_off(t->chan,old_port);
|
||||
if(t->port != (int)o->value){
|
||||
midi_track_off(i);
|
||||
t->port = (int)o->value;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void chancb(fltk::Widget* w, long i){
|
||||
//fltk::ValueInput* o = (fltk::ValueInput*)w;
|
||||
Gauge* o = (Gauge*)w;
|
||||
track* t = tracks[i];
|
||||
int old_chan = t->chan;
|
||||
t->chan = (int)o->value;
|
||||
midi_channel_off(old_chan,t->port);
|
||||
if(t->chan != (int)o->value){
|
||||
midi_track_off(i);
|
||||
t->chan = (int)o->value;
|
||||
}
|
||||
}
|
||||
|
||||
void progcb(fltk::Widget* w, long i){
|
||||
|
|
|
@ -481,7 +481,9 @@ void press_play(){
|
|||
}
|
||||
else{
|
||||
pause_backend();
|
||||
|
||||
all_notes_off();
|
||||
|
||||
ui->play_button->label("@>");
|
||||
}
|
||||
}
|
||||
|
@ -494,9 +496,14 @@ void press_stop(){
|
|||
}
|
||||
|
||||
pause_backend();
|
||||
reset_backend(left);
|
||||
|
||||
all_notes_off();
|
||||
|
||||
reset_backend(left);
|
||||
|
||||
|
||||
|
||||
|
||||
ui->song_timeline->update(left);
|
||||
ui->pattern_timeline->update(left);
|
||||
|
||||
|
|
|
@ -159,3 +159,5 @@ float randf(float l, float r){
|
|||
return ans;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -35,4 +35,9 @@ void unmodify_blocks();
|
|||
int unmodify_and_unstick_tracks();
|
||||
|
||||
float randf(float l, float r);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#endif
|
||||
|
|
Loading…
Reference in New Issue