/*
* Created on Jun 10, 2007
*
* Copyright (c) 2007 Jens Gulden
*
* http://www.frinika.com
*
* This file is part of Frinika.
*
* Frinika is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
* Frinika is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with Frinika; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package com.frinika.sequencer.model;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import javax.sound.midi.InvalidMidiDataException;
import javax.sound.midi.MidiEvent;
import javax.sound.midi.MidiMessage;
import javax.sound.midi.ShortMessage;
import com.frinika.sequencer.midi.groovepattern.GroovePattern;
import com.frinika.sequencer.midi.groovepattern.GroovePatternManager;
/**
* Encapsulates quantization by storing quatization options and bundling
* corresponding quantization methods.
*
* @author Jens Gulden
*/
public class Quantization implements Serializable {
private static final long serialVersionUID = 1L;
public int interval = 128 / 2; // default 1/8 note, TODO make dependent on sequencer's resolution
public float intensity = 1.0f;
public float swing = 0f;
public boolean quantizeNoteStart = true;
public boolean quantizeNoteLength = false;
public float smudge = 0.5f; // general groove-pattern-intensity parameter, interpretation dependent on GroovePattern
public float velocity = 0f; // intensity of adopting groove pattern's velocity
public transient GroovePattern groovePattern = null; // null for hard quantization
private String groovePatternName; // for serialization
/**
* Quantize a tick as requested by options.
*
* @param tick
* @param quant
* @param strength
* @param swing
* @param groovePattern
* @param grooveSmudge
* @param velocityByRef
* @return
*/
public long quantize(long tick, int[] velocityByRef) {
long t = tick + (interval/2);
long rest = t % interval ;
long target = t - rest; // hard-quantized to selected resolution
long swingShift = 0;
if (swing != 0.0f) {
if (((target / interval) % 2) == 1) { // every odd position in quantization-raster
swingShift += Math.round(interval * swing);
}
}
if (groovePattern != null) {
target = groovePattern.quantize(target, interval, smudge, velocityByRef); // de-quantize to feel (but remain at selected resolution)
}
target += swingShift;
int diff = (int)(target - tick);
int sDiff = Math.round(diff * intensity);
return tick + sDiff;
}
/**
* Quantize a single MidiEvent as requested by options. Either the original instance is returned unchanged
* (if no quantization has been applied), or a new MidiEvent object with a modified timestamp (and possibly
* changed other valued like e.g. velocity) is returned.
*/
public MidiEvent quantize(MidiEvent event) {
byte[] data = event.getMessage().getMessage();
int status = data[0] & 0xf0;
int vel = data[2];
if ( (quantizeNoteStart && (status == ShortMessage.NOTE_ON) && (vel != 0) )
|| (quantizeNoteLength && ( (status == ShortMessage.NOTE_OFF) || ( (vel == 0) && (status == ShortMessage.NOTE_ON) ) ) ) ) {
// (some devices use note_on with velocity 0 for note_off)
// quantize note length does not behave exactly the same when quantizing on-the-fly and offline
long tick = event.getTick();
int[] velocityByRef = { -1 };
long qTick = quantize(tick, velocityByRef);
MidiMessage message;
int targetVel = velocityByRef[0];
if ( (vel != 0) && (targetVel != -1) && (velocity != 0f) && (groovePattern != null)) { // change velocity value
int d = Math.round((targetVel - vel) * velocity);
vel += d;
if (vel < 1 ) vel = 1; else if (vel > 127) vel = 127;
message = new ShortMessage();
try {
((ShortMessage)message).setMessage(data[0], data[1], vel);
} catch (InvalidMidiDataException imde) {
System.err.println("something went wrong while quantizing a MidiEvent");
return event;
}
} else {
message = event.getMessage();
}
if ( ( (status == ShortMessage.NOTE_OFF) || (vel == 0) ) && ( qTick != tick ) ) { // note-off has been quantized
if (qTick > 0) qTick--; // end one earlier
}
MidiEvent newEvent = new MidiEvent(message, qTick);
return newEvent;
} else {
return event;
}
}
private final static int[] DUMMY = new int[1];
/**
* Quantize a NoteEvent.
*
* @param note
*/
public void quantize(NoteEvent note) {
if ( quantizeNoteStart ) {
long tick = note.getStartTick();
int[] velocityByRef = { -1 };
long qTick = quantize(tick, velocityByRef);
note.setStartTick(qTick);
int targetVel = velocityByRef[0];
if ((targetVel != -1) && (velocity != 0) && (groovePattern != null)) {
int vel = note.getVelocity();
vel = Math.round(vel + ( (targetVel - vel) * velocity ));
if (vel > 127) vel = 127; else if (vel < 1) vel = 1;
note.setVelocity(vel);
}
}
if ( quantizeNoteLength ) {
long len = note.getDuration();
long qLen = quantize(len, DUMMY);
if (qLen == 0) {
qLen = interval;
}
qLen--;
note.setDuration(qLen);
}
}
/**
* customize serialization
* @param out
* @throws IOException
*/
private void writeObject(ObjectOutputStream out) throws IOException {
if (groovePattern != null) {
groovePatternName = groovePattern.getName();
} else {
groovePatternName = null;
}
out.defaultWriteObject();
}
/**
* customize serialization
* @param out
* @throws IOException
*/
private void readObject(ObjectInputStream in) throws ClassNotFoundException, IOException {
in.defaultReadObject();
if (groovePatternName != null) {
groovePattern = GroovePatternManager.getInstance().getGroovePattern(groovePatternName);
} else {
groovePatternName = null;
}
}
}