Mercurial > hg > cmdp
view src/uk/ac/qmul/eecs/depic/daw/ClipList.java @ 4:473da40f3d39 tip
added html formatting to Daw/package-info.java
author | Fiore Martin <f.martin@qmul.ac.uk> |
---|---|
date | Thu, 25 Feb 2016 17:50:09 +0000 |
parents | 629262395647 |
children |
line wrap: on
line source
/* Cross-Modal DAW Prototype - Prototype of a simple Cross-Modal Digital Audio Workstation. Copyright (C) 2015 Queen Mary University of London (http://depic.eecs.qmul.ac.uk/) This program 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 3 of the License, or (at your option) any later version. This program 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 this program. If not, see <http://www.gnu.org/licenses/>. */ package uk.ac.qmul.eecs.depic.daw; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; /** * * A list of Clips * */ public class ClipList extends ArrayList<Clip> { private static final long serialVersionUID = 1L; private int scaleFactor; private List<Integer> starts; private Map<Sample,WavePeaks> peaksMap; private List<SoundWaveListener> changeListeners; private ClipListEditor clipListEditor; public ClipList(int scaleFactor, Map<Sample,WavePeaks> peaksMap,SoundWave wave) { this.scaleFactor = scaleFactor; this.peaksMap = peaksMap; changeListeners = new ArrayList<>(1); starts = new ArrayList<>(); clipListEditor = new ClipListEditor(); } /** * Adds a new {@code SampleSelection}. The order of selections is kept (ordered insert). * * @param s the new selection to add */ @Override public boolean add(Clip s){ int index = Collections.binarySearch(starts, s.getStart()); if(index < 0) index = ~index; starts.add(index, s.getStart()); super.add(index,s); return true; } /** * Unsupported method * * @throws UnsupportedOperationException * */ @Override public void add(int index, Clip element){ throw new UnsupportedOperationException(); } @Override public boolean addAll(Collection<? extends Clip > collection){ for(Clip c : collection){ add(c); } return (!collection.isEmpty()); } @Override public boolean addAll(int index, Collection<? extends Clip> c){ throw new UnsupportedOperationException(); } @Override public boolean removeAll(Collection<?> c){ boolean changed = false; for( Object o : c){ boolean removed = remove(o); changed = changed || removed; } return changed; } @Override public boolean retainAll(Collection<?> collection){ List<Clip> newList = new ArrayList<>(size()); for(Clip c : this){ if(collection.contains(c)){ newList.add(c); } } if(newList.size() == size()) return false; clear(); addAll(newList); return true; } @Override public boolean remove(Object c){ boolean removed = super.remove(c); if(removed){ starts.remove(((Clip)c).getStart()); // FIXME boh } return removed; } public void clear(){ super.clear(); starts.clear(); } public void addSoundWaveListener(SoundWaveListener l ){ changeListeners.add(l); } public boolean removeSoundWaveListener(SoundWaveListener l ){ return changeListeners.remove(l); } protected void fireSoundWaveListeners(SoundWaveEvent evt){ for(SoundWaveListener l : changeListeners){ l.update(evt); } } /** * Returns the length of the SoundWave * * @return */ public int getLength() { if(isEmpty()) return 0; else { int max = get(0).getEnd(); for(int i=1; i< size(); i++){ if(max < get(i).getEnd()){ max = get(i).getEnd(); } } return max+1; } } public float getLengthMs(){ if(isEmpty()) return 0.0f; else{ float max = get(0).getEndTimeMs(); for(int i=1; i< size(); i++){ if(max < get(i).getEndTimeMs()){ max = get(i).getEndTimeMs(); } } return max; } } public List<Clip> getClipsAt(int pos){ // FIXME maybe find a better algorithm if(isEmpty()) return Collections.emptyList(); List<Clip> clipsAt = new ArrayList<>(size()); for(Clip s : this){ /* take advantage of the fact that ClipList is ordered by start */ if(s.getStart() > pos){ break; }else if(s.contains(pos)){ clipsAt.add(s); } } // if(selections.get(chachedIndex).contains((int)p)){ // return selections.get(chachedIndex); // }else{ // for(int i=0;i<selections.size();i++){ // if(selections.get(i).contains(i)){ // chachedIndex = i; // return selections.get(chachedIndex); // } // } // } return clipsAt; } public List<Clip> getClipsAtTime(float time){ if(isEmpty()){ return Collections.emptyList(); } List<Clip> clipsAt = new ArrayList<>(size()); for(Clip s : this){ /* clips are ordered by start (and hence startTimeMs) so next clips will all have * * startTimeMs greater than time. Further check is therefore useless */ if(s.getStartTimeMs() > time){ break; }else if(s.containsTime(time)){ clipsAt.add(s); } } return clipsAt; } /** * changes the scale factor of this clip list. All the contained clips will be rescaled according to the new scale factor. * * @param newScaleFactor */ public void setScaleFactor(int newScaleFactor){ for(int i=0; i<size(); i++){ Clip clip = get(i); /* use the Selection static method to convert the clip start and end to the new scale factor values */ Selection converted = Selection.convertFactor(new Selection(clip.getStart(),clip.getEnd(),scaleFactor), newScaleFactor); /* replace the old clip with an update scale factor one */ set(i, new Clip( converted.getStart(), converted.getEnd(), clip.getSample(), Selection.convertFactor(new Selection(clip.getSampleStart(),clip.getEnd(),scaleFactor), newScaleFactor).getStart(), clip.getSample().getLength()/peaksMap.get(clip.getSample()).withScaleFactor(newScaleFactor).size())); } scaleFactor = newScaleFactor; } public int getScaleFactor(){ return scaleFactor; } public SoundWaveEditor getClipEditor () { return clipListEditor; } /* returns a new list obtained by splitting this at the given position */ private static List<Clip> splitList(int pos, List<Clip> list){ List<Clip> newList = new LinkedList<>(); for(int i=0; i<list.size(); i++){ Clip c = list.get(i); /* split around getStart() */ if(c.contains(pos)){ Clip [] split = c.split(pos); newList.add(split[0]); if(split.length == 2) newList.add(split[1]); }else{ newList.add(c); } } return newList; } private class ClipListEditor implements SoundWaveEditor { List<Clip> copiedClips; Selection copySelection; boolean copied; ClipListEditor(){ copiedClips = new LinkedList<>(); copySelection = Selection.ZERO_SELECTION; } @Override public boolean cut(SoundWave wave, Selection selection) { if(selection == null || selection.getStart() == selection.getEnd()){ return false; } /* cut also copies in memory */ copySelection = selection; /* splits this clipList on start and end of selection and * saves in copiedClips the clips within the selection */ /* get the split list, splitting by selection start */ List<Clip> splitList = ClipList.splitList(selection.getStart(),ClipList.this); /* substitute this list with the split list */ ClipList.this.clear(); ClipList.this.addAll(splitList); /* get the split list, splitting by selection end */ splitList = ClipList.splitList(selection.getEnd(),ClipList.this); /* substitute this list with the split list */ ClipList.this.clear(); ClipList.this.addAll(splitList); copiedClips.clear(); for(Clip c : ClipList.this){ /* when a clip is split at position, position will go with the right split * * this is why getStart() must be grater and equal and getEnd() lower but not equal */ if(c.getStart() >= selection.getStart() && c.getEnd() < selection.getEnd()){ copiedClips.add(c); } } copied = removeAll(copiedClips); if(copied){ fireSoundWaveListeners(new SoundWaveEvent(wave,SoundWaveEvent.CUT,copiedClips)); } return copied; } @Override public boolean copy(SoundWave wave, Selection selection) { if(selection == null || selection.getStart() == selection.getEnd()){ return false; } /* keep track of the selection used to copy */ copySelection = selection; /* splits on the selection start */ List<Clip> splitList = splitList(selection.getStart(),ClipList.this); Iterator<Clip> iterator = splitList.iterator(); /* removes all the clips at the left of selection start */ while(iterator.hasNext()){ Clip c = iterator.next(); if(c.getStart() < selection.getStart()){ // not equal because when splitting at position iterator.remove(); // position will go with the right split } } /* splits what's left at the selection end */ splitList = splitList(selection.getEnd(),splitList); iterator = splitList.iterator(); /* removes all the clips at the right of selectino end */ while(iterator.hasNext()){ Clip c = iterator.next(); if(c.getEnd() >= selection.getEnd()){ iterator.remove(); } } copiedClips = splitList; if(splitList.isEmpty()){ return false; }else{ fireSoundWaveListeners(new SoundWaveEvent(wave,SoundWaveEvent.COPY,copiedClips)); return true; } } @Override public boolean paste(SoundWave wave, int position) { if(copiedClips.isEmpty()){ return false; } /* shift the clips to the new position */ for(Clip c : copiedClips){ /* shift the clip to the new position. Keeps into account the position relative * * to the original copy selection (c.getStart() - copySelection.getStart()). * * This is to have the clips original to be pasted as they were in the original * * selection, avoid them to all be to be placed with start at position */ Clip newClip = new Clip(c); newClip.setStartAt(position + c.getStart() - copySelection.getStart()); /* add the new clips after shifting them to the new position */ add(newClip); } fireSoundWaveListeners(new SoundWaveEvent(wave,SoundWaveEvent.PASTE,copiedClips)); return true; } @Override public boolean insert(SoundWave wave, int position){ if(copiedClips.isEmpty()){ return false; } /* split the list at position */ List<Clip> splitList = ClipList.splitList(position, ClipList.this); /* shift right all the clips at the right of position */ for(Clip c : splitList){ if(c.getStart() >= position){ // >= because when split at position, position will go to the right split c.shift((int)copySelection.lenght()); } } /* insert the copied clips */ for(Clip c : copiedClips){ /* shift the clip to the new position. Keeps into account the position relative * * to the original copy selection (c.getStart() - copySelection.getStart()). * * This is to have the clips original to be pasted as they were in the original * * selection, avoid them to all be to be placed with start at position */ Clip newClip = new Clip(c); newClip.setStartAt(position + c.getStart() - copySelection.getStart()); /* add the new clips after shifting them to the new position */ splitList.add(newClip); } /* update this clip list with the new list */ ClipList.this.clear(); ClipList.this.addAll(splitList); fireSoundWaveListeners(new SoundWaveEvent(wave,SoundWaveEvent.INSERT,copiedClips)); return true; } } }