/* Copyright (C) 2006 Christian Schneider
*
* This file is part of Nomad.
*
* Nomad 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.
*
* Nomad 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 Nomad; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
/*
* Created on Dec 21, 2006
*/
package net.sf.nmedit.jpatch.clavia.nordmodular.parser;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import net.sf.nmedit.jpatch.PConnection;
import net.sf.nmedit.jpatch.PConnector;
import net.sf.nmedit.jpatch.InvalidDescriptorException;
import net.sf.nmedit.jpatch.PModule;
import net.sf.nmedit.jpatch.PModuleContainer;
import net.sf.nmedit.jpatch.clavia.nordmodular.Format;
import net.sf.nmedit.jpatch.clavia.nordmodular.Knob;
import net.sf.nmedit.jpatch.clavia.nordmodular.MidiController;
import net.sf.nmedit.jpatch.clavia.nordmodular.NMPatch;
import net.sf.nmedit.jpatch.clavia.nordmodular.Note;
import net.sf.nmedit.jpatch.clavia.nordmodular.NoteSet;
import net.sf.nmedit.jpatch.clavia.nordmodular.PNMMorphSection;
import net.sf.nmedit.jpatch.clavia.nordmodular.VoiceArea;
import net.sf.nmedit.jpatch.clavia.nordmodular.PNMMorphSection.Assignments;
import net.sf.nmedit.jpatch.PParameter;
public class PatchExporter
{
private final boolean MorphMapDumpBugEnabled = true;//false;
private final boolean sortCables = true;
public void export (NMPatch p, PContentHandler handler) throws ParseException
{
handler.beginDocument();
// header
handler.beginSection(PParser.IHEADER,-1);
handler.header("Version", "Nord Modular patch 3.0");
handler.header(p.getHeader().getData());
handler.endSection(PParser.IHEADER);
//
{ // module dump
moduleDump(handler, p.getPolyVoiceArea());
moduleDump(handler, p.getCommonVoiceArea());
}
{ // current note dump
NoteSet noteSet = p.getNoteSet();
if (noteSet.size()>0)
{
handler.beginSection(PParser.ICURRENTNOTEDUMP,-1);
int[] record = getRecord(3);
for (Note n : noteSet)
{
record[0] = n.getNoteNumber();
record[1] = n.getAttackVelocity();
record[2] = n.getReleaseVelocity();
handler.currentNoteDump(record);
}
handler.endSection(PParser.ICURRENTNOTEDUMP);
}
}
{ // cable dump
cableDump(handler, p.getPolyVoiceArea());
cableDump(handler, p.getCommonVoiceArea());
}
{ // ParameterDump
parameterDump(handler, p.getPolyVoiceArea());
parameterDump(handler, p.getCommonVoiceArea());
}
{ // MorphMapDump
// first : morph group
PNMMorphSection morphs = p.getMorphSection();
int size = 0;
if (MorphMapDumpBugEnabled)
{
for (int i=0;i<morphs.getMorphCount();i++)
{
size+= morphs.getAssignments(i).size();
}
}
else
{
size = 1; // >0
}
if (size>0)
{
// this reproduces a bug were morph values are not written
// when no knob is assigned to any morph
handler.beginSection(PParser.IMORPHMAPDUMP, -1);
{
int[] record = getRecord(4);
for (int i=0;i<morphs.getMorphCount();i++)
{
record[i] = morphs.getMorph(i).getValue();
}
handler.morphMapDumpProlog(record);
}
int[] record = getRecord(5);
for (int i=0;i<morphs.getMorphCount();i++)
{
Assignments assignments = morphs.getAssignments(i);
PParameter morph = morphs.getMorph(i);
if (assignments.size()>0)
{
for (PParameter pp : assignments)
{
// TODO: in the presence of custo; parameter helper.index does not return
// the same value as getDescriptorindex
int pindex = Helper.index(pp); //pp.getDescriptor().getDescriptorIndex();
//System.out.println(pp.getDescriptor().getDescriptorIndex()+" "+pindex);
PModule m = pp.getParentComponent();
PParameter morphRange;
try
{
morphRange = Helper.getParameter(m, "morph", Helper.index(pp));
}
catch (InvalidDescriptorException e)
{
throw new ParseException(e);
}
record[0] = m.getParentComponent().getComponentIndex();
record[1] = m.getComponentIndex();
record[2] = pindex;
record[3] = i;
record[4] = morphRange.getValue();
handler.morphMapDump(record);
}
}
}
handler.endSection(PParser.IMORPHMAPDUMP);
}
}
{ // KeyboardAssignment
PNMMorphSection morphs = p.getMorphSection();
int [] record = getRecord(morphs.getMorphCount());
boolean writeKA = false;
for (int i=0;i<morphs.getMorphCount();i++)
{
int v = morphs.getKeyboardAssignment(i).getValue();
writeKA |= (v!=0); // write section only when at least one is != 0
record[i]=v;
}
if (writeKA)
{
handler.beginSection(PParser.IKEYBOARDASSIGNMENT, -1);
handler.keyboardAssignment(record);
handler.endSection(PParser.IKEYBOARDASSIGNMENT);
}
}
{ // KnobMapDump
Iterator<Knob> iter = p.getKnobs().iterator();
boolean hasAssignedKnobs = false;
int[] record = getRecord(4);
while (iter.hasNext())
{
Knob k = iter.next();
if (k.getParameter() != null)
{
if (!hasAssignedKnobs)
{
hasAssignedKnobs = true;
handler.beginSection(PParser.IKNOBMAPDUMP, -1);
}
String pclass = Helper.pclass(k.getParameter());
if ("parameter".equals(pclass))
{
PParameter pp = k.getParameter();
PModule m = pp.getParentComponent();
PModuleContainer va = m.getParentComponent();
record[0] = va.getComponentIndex();
record[1] = m.getComponentIndex();
record[2] = Helper.index(pp);
record[3] = k.getID();
handler.knobMapDump(record);
}
else if ("morph".equals(pclass))
{
PParameter morph = k.getParameter();
record[0] = Format.VALUE_SECTION_MORPH;
record[1] = 1; // module index = const(1)
record[2] = Helper.index(morph);
record[3] = k.getID();
handler.knobMapDump(record);
}
}
}
if (hasAssignedKnobs)
{
handler.endSection(PParser.IKNOBMAPDUMP);
}
}
{ // CtrlMapDump
Iterator<MidiController> iter = p.getMidiControllers().iterator();
boolean hasAssignedMCtrl = false;
int[] record = getRecord(4);
while (iter.hasNext())
{
MidiController mc = iter.next();
if (mc.getParameter() != null)
{
if (!hasAssignedMCtrl)
{
hasAssignedMCtrl = true;
handler.beginSection(PParser.ICTRLMAPDUMP, -1);
}
String pclass = Helper.pclass(mc.getParameter());
if ("parameter".equals(pclass))
{
PParameter pp = mc.getParameter();
PModule m = pp.getParentComponent();
PModuleContainer va = m.getParentComponent();
record[0] = va.getComponentIndex(); // voice area id
record[1] = m.getComponentIndex(); // index in module container
record[2] = Helper.index(pp);
record[3] = mc.getControlId();
handler.ctrlMapDump(record);
}
else if ("morph".equals(pclass))
{
PParameter morph = mc.getParameter();
record[0] = Format.VALUE_SECTION_MORPH;
record[1] = 1; // module index = const(1)
record[2] = Helper.index(morph); // morph index [0..3]
record[3] = mc.getControlId();
handler.ctrlMapDump(record);
}
}
}
if (hasAssignedMCtrl)
handler.endSection(PParser.ICTRLMAPDUMP);
}
{ // CustomDump
customDump(handler, p.getPolyVoiceArea());
customDump(handler, p.getCommonVoiceArea());
}
{ // NameDump
nameDump(handler, p.getPolyVoiceArea());
nameDump(handler, p.getCommonVoiceArea());
}
// notes
handler.beginSection(PParser.INOTES, -1);
String note = p.getNote();
if (note == null)
note = "";
// fix newline characters
note = note.replaceAll("\\r", "").replaceAll("\\n","\r\n");
handler.notes(note);
handler.endSection(PParser.INOTES);
handler.endDocument();
}
private int vaId(VoiceArea va)
{
if (va == null) return 2;
else return va.isPolyVoiceArea() ? 1 : 0;
}
private void parameterDump( PContentHandler handler, VoiceArea voiceArea ) throws ParseException
{
handler.beginSection(PParser.IPARAMETERDUMP, vaId(voiceArea));
for (PModule m : voiceArea)
{
PModule nm = (PModule) m;
if (m.getParameterCount()>0)
{
int pcount = Helper.getParameterClassCount(nm, "parameter");
int[] record = getRecord(3+pcount);
record[0] = nm.getComponentIndex(); // index in module container
record[1] = Helper.index(nm); // module id
record[2] = pcount;
for (int i=0;i<pcount;i++)
record[3+i]=Helper.getParameter(nm, "parameter", i).getValue();
handler.parameterDump(record);
}
}
handler.endSection(PParser.IPARAMETERDUMP);
}
private void customDump( PContentHandler handler, VoiceArea voiceArea ) throws ParseException
{
handler.beginSection(PParser.ICUSTOMDUMP, vaId(voiceArea));
for (PModule m : voiceArea)
{
PModule nm = (PModule) m;
int ccount = Helper.getParameterClassCount(nm, "custom");
if (ccount>0)
{
int[] record = getRecord(2+ccount);
record[0] = nm.getComponentIndex(); // index in module container
record[1] = ccount;
for (int i=0;i<ccount;i++)
record[2+i]=Helper.getParameter(nm, "custom", i).getValue();
handler.customDump(record);
}
}
handler.endSection(PParser.ICUSTOMDUMP);
}
private void moduleDump( PContentHandler handler, VoiceArea voiceArea ) throws ParseException
{
handler.beginSection(PParser.IMODULEDUMP, vaId(voiceArea));
int[] record = getRecord(4);
for (PModule mm : voiceArea)
{
PModule m = (PModule)mm;
record[0] = m.getComponentIndex();
record[1] = Helper.index(m);
record[2] = m.getInternalX();
record[3] = m.getInternalY();
handler.moduleDump(record);
}
handler.endSection(PParser.IMODULEDUMP);
}
private void nameDump( PContentHandler handler, VoiceArea voiceArea ) throws ParseException
{
handler.beginSection(PParser.INAMEDUMP, vaId(voiceArea));
for (PModule mm : voiceArea)
{
PModule m = (PModule)mm;
handler.moduleNameDump(m.getComponentIndex(), m.getTitle());
}
handler.endSection(PParser.INAMEDUMP);
}
private void getConnectionRecord(int[] record, PConnection c)
{
PConnector dst = c.getA();
PConnector src = c.getB();
// check order
if (dst.isOutput())
{
PConnector tmp = dst;
dst = src;
src = tmp;
}
record[0] = src.getSignalType().getId();
record[1] = dst.getParentComponent().getComponentIndex();
record[2] = Helper.index(dst);
record[3] = 0;
record[4] = src.getParentComponent().getComponentIndex();
record[5] = Helper.index(src);
record[6] = src.isOutput()?1:0;
}
private void cableDump( PContentHandler handler, VoiceArea voiceArea ) throws ParseException
{
handler.beginSection(PParser.ICABLEDUMP, vaId(voiceArea));
final int rsize = 7;
if (!sortCables)
{
int[] record = getRecord(rsize);
for(PConnection c: voiceArea.getConnectionManager())
{
getConnectionRecord(record, c);
handler.cableDump(record);
}
}
else
{
List<int[]> cables = new ArrayList<int[]>(100);
{
for (PConnection c: voiceArea.getConnectionManager())
{
int[] r = new int[rsize];
getConnectionRecord(r, c);
// if both connectors are inputs (0)
// then we assure that the first connector in the record
// has a module index less or equal than the second one
if (r[3] == 0 && r[6] == 0 && (r[1]>r[4] || (r[1]==r[4] && r[2]>r[5])))
{
for (int i=1;i<3+1;i++)
{
int t = r[i];
r[i] = r[i+3];
r[i+3]= t;
}
}
cables.add(r);
}
}
Collections.sort(cables, new CableSort());
Iterator<int[]> iter = cables.iterator();
while (iter.hasNext())
{
handler.cableDump(iter.next());
}
}
handler.endSection(PParser.ICABLEDUMP);
}
private int[] cachedRecord = new int[10];
private int[] getRecord(int size)
{
if (cachedRecord.length<size)
cachedRecord = new int[size];
return cachedRecord;
}
private static class CableSort implements Comparator<int[]>
{
// sorting order
final static int[] permutation = new int[] { 0, 1, 3, 2, 4, 6, 5 };
public int compare( int[] o1, int[] o2 )
{
int p;
int r;
for (int i=0;i<permutation.length;i++)
{
p = permutation[i];
r = o1[p]-o2[p];
if (r!=0)
return r;
}
return 0;
}
}
}