Alg_event_ptr Alg_seq::write_track_name(ostream &file, int n, Alg_events &events) // write #track <n> <trackname-or-sequencename> // if we write the name on the "#track" line, then we do *not* want // to write again as an update: "-seqnames:"Jordu", so if we do // find a name and write it, return a pointer to it so the track // writer knows what update (if any) to skip { Alg_event_ptr e = NULL; file << "#track " << n; const char *attr = symbol_table.insert_string( n == 0 ? "seqnames" : "tracknames"); // search for name in events with timestamp of 0 for (int i = 0; i < events.length(); i++) { e = events[i]; if (e->time > 0) break; if (e->is_update()) { Alg_update_ptr u = (Alg_update_ptr) e; if (u->parameter.attr == attr) { file << " " << u->parameter.s; break; } } } file << endl; return e; }
/* EDIT_TRANSCRIPTION edit the allegro time map structure according to the warping and output a midi file and transcription file */ void edit_transcription(Alg_seq &seq , bool warp, FILE *outf, const char *midi_filename, const char *beat_filename) { int note_x = 1; seq.convert_to_seconds(); Alg_iterator iter(&seq, false); // no note-offs iter.begin(); Alg_event_ptr e = iter.next(); while (e) { if (e->is_note()) { Alg_note_ptr n = (Alg_note_ptr) e; fprintf(outf, "%d %ld %d %d ", note_x++, n->chan, ROUND(n->pitch), ROUND(n->loud)); // now compute onset time mapped to audio time double start = n->time; double finish = n->time + n->dur; if (warp) { start = sa.map_time(start); finish = sa.map_time(finish); } fprintf(outf, "%.3f %.3f\n", start, finish-start); } e = iter.next(); } iter.end(); fclose(outf); if (warp) { // align the midi file and write out sa.midi_tempo_align(seq); seq.smf_write(midi_filename); print_beat_map(seq, beat_filename); } }
bool ImportMIDI(wxString fName, NoteTrack * dest) { if (fName.Length() <= 4){ wxMessageBox( _("Could not open file ") + fName + _(": Filename too short.")); return false; } bool is_midi = false; if (fName.Right(4).CmpNoCase(wxT(".mid")) == 0 || fName.Right(5).CmpNoCase(wxT(".midi")) == 0) is_midi = true; else if(fName.Right(4).CmpNoCase(wxT(".gro")) != 0) { wxMessageBox( _("Could not open file ") + fName + _(": Incorrect filetype.")); return false; } wxFFile mf(fName, wxT("rb")); if (!mf.IsOpened()) { wxMessageBox( _("Could not open file ") + fName + wxT(".")); return false; } double offset = 0.0; Alg_seq_ptr new_seq = new Alg_seq(fName.mb_str(), is_midi, &offset); //Should we also check if(seq->tracks() == 0) ? if(new_seq->get_read_error() == alg_error_open){ wxMessageBox( _("Could not open file ") + fName + wxT(".")); mf.Close(); delete new_seq; return false; } dest->SetSequence(new_seq); dest->SetOffset(offset); wxString trackNameBase = fName.AfterLast(wxFILE_SEP_PATH).BeforeLast('.'); dest->SetName(trackNameBase); mf.Close(); // the mean pitch should be somewhere in the middle of the display Alg_iterator iterator(new_seq, false); iterator.begin(); // for every event Alg_event_ptr evt; int note_count = 0; int pitch_sum = 0; while ((evt = iterator.next())) { // if the event is a note if (evt->get_type() == 'n') { Alg_note_ptr note = (Alg_note_ptr) evt; pitch_sum += (int) note->pitch; note_count++; } } int mean_pitch = (note_count > 0 ? pitch_sum / note_count : 60); // initial track is about 27 half-steps high; if bottom note is C, // then middle pitch class is D. Round mean_pitch to the nearest D: int mid_pitch = ((mean_pitch - 2 + 6) / 12) * 12 + 2; dest->SetBottomNote(mid_pitch - 14); return true; }
void NoteTrack::WarpAndTransposeNotes(double t0, double t1, const TimeWarper &warper, double semitones) { // Since this is a duplicate and duplicates convert mSeq to // a text string for saving as XML, we probably have to // duplicate again to get back an mSeq NoteTrack *nt = this; double offset = nt->GetOffset(); // track is shifted this amount if (!mSeq) { // replace saveme with an (unserialized) duplicate nt = (NoteTrack *) this->Duplicate(); wxASSERT(!mSeq && nt->mSeq && !nt->mSerializationBuffer); // swap mSeq and Buffer between this and nt nt->mSerializationBuffer = mSerializationBuffer; nt->mSerializationLength = mSerializationLength; mSerializationBuffer = NULL; mSerializationLength = 0; mSeq = nt->mSeq; nt->mSeq = NULL; delete nt; // delete the duplicate } mSeq->convert_to_seconds(); // make sure time units are right t1 -= offset; // adjust time range to compensate for track offset t0 -= offset; if (t1 > mSeq->get_dur()) { // make sure t0, t1 are within sequence t1 = mSeq->get_dur(); if (t0 >= t1) return; } Alg_iterator iter(mSeq, false); iter.begin(); Alg_event_ptr event; while ((event = iter.next()) && event->time < t1) { if (event->is_note() && event->time >= t0 && // Allegro data structure does not restrict channels to 16. // Since there is not way to select more than 16 channels, // map all channel numbers mod 16. This will have no effect // on MIDI files, but it will allow users to at least select // all channels on non-MIDI event sequence data. IsVisibleChan(event->chan % 16)) { event->set_pitch(event->get_pitch() + semitones); } } iter.end(); // now, use warper to warp the tempo map mSeq->convert_to_beats(); // beats remain the same Alg_time_map_ptr map = mSeq->get_time_map(); map->insert_beat(t0, map->time_to_beat(t0)); map->insert_beat(t1, map->time_to_beat(t1)); int i, len = map->length(); for (i = 0; i < len; i++) { Alg_beat &beat = map->beats[i]; beat.time = warper.Warp(beat.time + offset) - offset; } // about to redisplay, so might as well convert back to time now mSeq->convert_to_seconds(); }
int find_midi_duration(Alg_seq &seq, float *dur) { *dur = 0.0F; int nnotes = 0; int i, j; seq.convert_to_seconds(); for (j = 0; j < seq.track_list.length(); j++) { Alg_events ¬es = (seq.track_list[j]); for (i = 0; i < notes.length(); i++) { Alg_event_ptr e = notes[i]; if (e->is_note()) { Alg_note_ptr n = (Alg_note_ptr) e; float note_end = float(n->time + n->dur); if (note_end > *dur) *dur = note_end; nnotes++; } } } return nnotes; }
bool MidiImport::readSMF( TrackContainer* tc ) { QString filename = file().fileName(); closeFile(); const int preTrackSteps = 2; QProgressDialog pd( TrackContainer::tr( "Importing MIDI-file..." ), TrackContainer::tr( "Cancel" ), 0, preTrackSteps, gui->mainWindow() ); pd.setWindowTitle( TrackContainer::tr( "Please wait..." ) ); pd.setWindowModality(Qt::WindowModal); pd.setMinimumDuration( 0 ); pd.setValue( 0 ); Alg_seq_ptr seq = new Alg_seq(filename.toLocal8Bit(), true); seq->convert_to_beats(); pd.setMaximum( seq->tracks() + preTrackSteps ); pd.setValue( 1 ); // 128 CC + Pitch Bend smfMidiCC ccs[129]; smfMidiChannel chs[256]; MeterModel & timeSigMM = Engine::getSong()->getTimeSigModel(); AutomationPattern * timeSigNumeratorPat = AutomationPattern::globalAutomationPattern( &timeSigMM.numeratorModel() ); AutomationPattern * timeSigDenominatorPat = AutomationPattern::globalAutomationPattern( &timeSigMM.denominatorModel() ); // TODO: adjust these to Time.Sig changes double beatsPerTact = 4; double ticksPerBeat = DefaultTicksPerTact / beatsPerTact; // Time-sig changes Alg_time_sigs * timeSigs = &seq->time_sig; for( int s = 0; s < timeSigs->length(); ++s ) { Alg_time_sig timeSig = (*timeSigs)[s]; // Initial timeSig, set song-default value if(/* timeSig.beat == 0*/ true ) { // TODO set song-global default value printf("Another timesig at %f\n", timeSig.beat); timeSigNumeratorPat->putValue( timeSig.beat*ticksPerBeat, timeSig.num ); timeSigDenominatorPat->putValue( timeSig.beat*ticksPerBeat, timeSig.den ); } else { } } pd.setValue( 2 ); // Tempo stuff AutomationPattern * tap = tc->tempoAutomationPattern(); if( tap ) { tap->clear(); Alg_time_map * timeMap = seq->get_time_map(); Alg_beats & beats = timeMap->beats; for( int i = 0; i < beats.len - 1; i++ ) { Alg_beat_ptr b = &(beats[i]); double tempo = ( beats[i + 1].beat - b->beat ) / ( beats[i + 1].time - beats[i].time ); tap->putValue( b->beat * ticksPerBeat, tempo * 60.0 ); } if( timeMap->last_tempo_flag ) { Alg_beat_ptr b = &( beats[beats.len - 1] ); tap->putValue( b->beat * ticksPerBeat, timeMap->last_tempo * 60.0 ); } } // Song events for( int e = 0; e < seq->length(); ++e ) { Alg_event_ptr evt = (*seq)[e]; if( evt->is_update() ) { printf("Unhandled SONG update: %d %f %s\n", evt->get_type_code(), evt->time, evt->get_attribute() ); } } // Tracks for( int t = 0; t < seq->tracks(); ++t ) { QString trackName = QString( tr( "Track" ) + " %1" ).arg( t ); Alg_track_ptr trk = seq->track( t ); pd.setValue( t + preTrackSteps ); for( int c = 0; c < 129; c++ ) { ccs[c].clear(); } // Now look at events for( int e = 0; e < trk->length(); ++e ) { Alg_event_ptr evt = (*trk)[e]; if( evt->chan == -1 ) { bool handled = false; if( evt->is_update() ) { QString attr = evt->get_attribute(); if( attr == "tracknames" && evt->get_update_type() == 's' ) { trackName = evt->get_string_value(); handled = true; } } if( !handled ) { // Write debug output printf("MISSING GLOBAL HANDLER\n"); printf(" Chn: %d, Type Code: %d, Time: %f", (int) evt->chan, evt->get_type_code(), evt->time ); if ( evt->is_update() ) { printf( ", Update Type: %s", evt->get_attribute() ); if ( evt->get_update_type() == 'a' ) { printf( ", Atom: %s", evt->get_atom_value() ); } } printf( "\n" ); } } else if( evt->is_note() && evt->chan < 256 ) { smfMidiChannel * ch = chs[evt->chan].create( tc, trackName ); Alg_note_ptr noteEvt = dynamic_cast<Alg_note_ptr>( evt ); int ticks = noteEvt->get_duration() * ticksPerBeat; Note n( (ticks < 1 ? 1 : ticks ), noteEvt->get_start_time() * ticksPerBeat, noteEvt->get_identifier() - 12, noteEvt->get_loud()); ch->addNote( n ); } else if( evt->is_update() ) { smfMidiChannel * ch = chs[evt->chan].create( tc, trackName ); double time = evt->time*ticksPerBeat; QString update( evt->get_attribute() ); if( update == "programi" ) { long prog = evt->get_integer_value(); if( ch->isSF2 ) { ch->it_inst->childModel( "bank" )->setValue( 0 ); ch->it_inst->childModel( "patch" )->setValue( prog ); } else { const QString num = QString::number( prog ); const QString filter = QString().fill( '0', 3 - num.length() ) + num + "*.pat"; const QString dir = "/usr/share/midi/" "freepats/Tone_000/"; const QStringList files = QDir( dir ). entryList( QStringList( filter ) ); if( ch->it_inst && !files.empty() ) { ch->it_inst->loadFile( dir+files.front() ); } } } else if( update.startsWith( "control" ) || update == "bendr" ) { int ccid = update.mid( 7, update.length()-8 ).toInt(); if( update == "bendr" ) { ccid = 128; } if( ccid <= 128 ) { double cc = evt->get_real_value(); AutomatableModel * objModel = NULL; switch( ccid ) { case 0: if( ch->isSF2 && ch->it_inst ) { objModel = ch->it_inst->childModel( "bank" ); printf("BANK SELECT %f %d\n", cc, (int)(cc*127.0)); cc *= 127.0f; } break; case 7: objModel = ch->it->volumeModel(); cc *= 100.0f; break; case 10: objModel = ch->it->panningModel(); cc = cc * 200.f - 100.0f; break; case 128: objModel = ch->it->pitchModel(); cc = cc * 100.0f; break; default: //TODO: something useful for other CCs break; } if( objModel ) { if( time == 0 && objModel ) { objModel->setInitValue( cc ); } else { if( ccs[ccid].at == NULL ) { ccs[ccid].create( tc, trackName + " > " + ( objModel != NULL ? objModel->displayName() : QString("CC %1").arg(ccid) ) ); } ccs[ccid].putValue( time, objModel, cc ); } } } } else { printf("Unhandled update: %d %d %f %s\n", (int) evt->chan, evt->get_type_code(), evt->time, evt->get_attribute() ); } } } } delete seq; for( int c=0; c < 256; ++c ) { if( !chs[c].hasNotes && chs[c].it ) { printf(" Should remove empty track\n"); // must delete trackView first - but where is it? //tc->removeTrack( chs[c].it ); //it->deleteLater(); } } // Set channel 10 to drums as per General MIDI's orders if( chs[9].hasNotes && chs[9].it_inst && chs[9].isSF2 ) { // AFAIK, 128 should be the standard bank for drums in SF2. // If not, this has to be made configurable. chs[9].it_inst->childModel( "bank" )->setValue( 128 ); chs[9].it_inst->childModel( "patch" )->setValue( 0 ); } return true; }
void Alg_seq::write(ostream &file, bool in_secs) { int i, j; if (in_secs) convert_to_seconds(); else convert_to_beats(); Alg_event_ptr update_to_skip = write_track_name(file, 0, track_list[0]); Alg_beats &beats = time_map->beats; for (i = 0; i < beats.len - 1; i++) { Alg_beat_ptr b = &(beats[i]); if (in_secs) { file << "T" << TIMFMT << b->time; } else { file << "TW" << TIMFMT << b->beat / 4; } double tempo = (beats[i + 1].beat - b->beat) / (beats[i + 1].time - beats[i].time); file << " -tempor:" << GFMT << tempo * 60 << "\n"; } if (time_map->last_tempo_flag) { // we have final tempo: Alg_beat_ptr b = &(beats[beats.len - 1]); if (in_secs) { file << "T" << TIMFMT << b->time; } else { file << "TW" << TIMFMT << b->beat / 4; } file << " -tempor:" << GFMT << time_map->last_tempo * 60.0 << "\n"; } // write the time signatures for (i = 0; i < time_sig.length(); i++) { Alg_time_sig &ts = time_sig[i]; double time = ts.beat; if (in_secs) { file << "T" << TIMFMT << time << " V- -timesig_numr:" << GFMT << ts.num << "\n"; file << "T" << TIMFMT << time << " V- -timesig_denr:" << GFMT << ts.den << "\n"; } else { double wholes = ts.beat / 4; file << "TW" << TIMFMT << wholes << " V- -timesig_numr:" << GFMT << ts.num << "\n"; file << "TW" << TIMFMT << wholes << " V- -timesig_denr:" << GFMT << ts.den << "\n"; } } for (j = 0; j < track_list.length(); j++) { Alg_events ¬es = track_list[j]; if (j != 0) update_to_skip = write_track_name(file, j, notes); // now write the notes at beat positions for (i = 0; i < notes.length(); i++) { Alg_event_ptr e = notes[i]; // if we already wrote this event as a track or sequence name, // do not write it again if (e == update_to_skip) continue; double start = e->time; if (in_secs) { file << "T" << TIMFMT << start; } else { file << "TW" << TIMFMT << start / 4; } // write the channel as Vn or V- if (e->chan == -1) file << " V-"; else file << " V" << e->chan; // write the note or update data if (e->is_note()) { Alg_note_ptr n = (Alg_note_ptr) e; double dur = n->dur; file << " K" << n->get_identifier() << " P" << GFMT << n->pitch; if (in_secs) { file << " U" << TIMFMT << dur; } else { file << " Q" << TIMFMT << dur; } file << " L" << GFMT << n->loud; Alg_parameters_ptr p = n->parameters; while (p) { parameter_print(file, &(p->parm)); p = p->next; } } else { // an update assert(e->is_update()); Alg_update_ptr u = (Alg_update_ptr) e; if (u->get_identifier() != -1) { file << " K" << u->get_identifier(); } parameter_print(file, &(u->parameter)); } file << "\n"; } } }