aboutsummaryrefslogtreecommitdiffstats
path: root/src/com/jsyn/ports/SequentialDataCrossfade.java
blob: 25e1fd99fafe216e0471f243fc4f59c6620b9ea9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
/*
 * Copyright 2009 Phil Burk, Mobileer Inc
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.jsyn.ports;

import com.jsyn.data.SequentialData;
import com.jsyn.data.SequentialDataCommon;

/**
 * A SequentialData object that will crossfade between two other SequentialData objects. The
 * crossfade is linear. This could, for example, be used to create a smooth transition between two
 * samples, or between two arbitrary regions in one sample. As an example, consider a sample that
 * has a length of 200000 frames. You could specify a sample loop that started arbitrarily at frame
 * 50000 and with a size of 30000 frames. Unless you got lucky with the zero crossings, it is likely
 * that you will hear a pop when this sample loops. To prevent the pop you could crossfade the
 * beginning of the loop with the region immediately after the end of the loop. To crossfade with
 * 5000 samples after the loop:
 * 
 * <pre>
 * SequentialDataCrossfade xfade = new SequentialDataCrossfade(sample, (50000 + 30000), 5000, sample,
 *         50000, 30000);
 * </pre>
 * 
 * After the crossfade you will hear the rest of the target at full volume. There are two regions
 * that determine what is returned from readDouble()
 * <ol>
 * <li>Crossfade region with size crossFadeFrames. It fades smoothly from source to target.</li>
 * <li>Steady region that is simply the target values with size (numFrames-crossFadeFrames).</li>
 * </ol>
 * 
 * <pre>
 *     "Crossfade Region"      "Steady Region"
 * |-- source fading out --|
 * |-- target fading in  --|-- remainder of target at original volume --|
 * </pre>
 * 
 * @author Phil Burk
 */
class SequentialDataCrossfade extends SequentialDataCommon {
    private SequentialData source;
    private int sourceStartIndex;

    private SequentialData target;
    private int targetStartIndex;

    private int crossFadeFrames;
    private double frameScaler;

    /**
     * @param source SequentialData that will be at full volume at the beginning of the crossfade
     *            region.
     * @param sourceStartFrame Frame in source to begin the crossfade.
     * @param crossFadeFrames Number of frames in the crossfaded region.
     * @param target SequentialData that will be at full volume at the end of the crossfade region.
     * @param targetStartFrame Frame in target to begin the crossfade.
     * @param numFrames total number of frames in this data object.
     */
    public void setup(SequentialData source, int sourceStartFrame, int crossFadeFrames,
            SequentialData target, int targetStartFrame, int numFrames) {

        assert ((sourceStartFrame + crossFadeFrames) <= source.getNumFrames());
        assert ((targetStartFrame + numFrames) <= target.getNumFrames());

        // System.out.println( "WARNING! sourceStartFrame = " + sourceStartFrame
        // + ", crossFadeFrames = " + crossFadeFrames + ", maxFrame = "
        // + source.getNumFrames() + ", source = " + source );
        // System.out.println( "  targetStartFrame = " + targetStartFrame
        // + ", numFrames = " + numFrames + ", maxFrame = "
        // + target.getNumFrames() + ", target = " + target );

        // There is a danger that we might nest SequentialDataCrossfades deeply
        // as source. If past crossfade region then pull out the target.
        if (source instanceof SequentialDataCrossfade) {
            SequentialDataCrossfade crossfade = (SequentialDataCrossfade) source;
            // If we are starting past the crossfade region then just use the
            // target.
            if (sourceStartFrame >= crossfade.crossFadeFrames) {
                source = crossfade.target;
                sourceStartFrame += crossfade.targetStartIndex / source.getChannelsPerFrame();
            }
        }

        if (target instanceof SequentialDataCrossfade) {
            SequentialDataCrossfade crossfade = (SequentialDataCrossfade) target;
            target = crossfade.target;
            targetStartFrame += crossfade.targetStartIndex / target.getChannelsPerFrame();
        }

        this.source = source;
        this.target = target;
        this.sourceStartIndex = sourceStartFrame * source.getChannelsPerFrame();
        this.crossFadeFrames = crossFadeFrames;
        this.targetStartIndex = targetStartFrame * target.getChannelsPerFrame();

        frameScaler = (crossFadeFrames == 0) ? 1.0 : (1.0 / crossFadeFrames);
        this.numFrames = numFrames;
    }

    @Override
    public void writeDouble(int index, double value) {
    }

    @Override
    public double readDouble(int index) {
        int frame = index / source.getChannelsPerFrame();
        if (frame < crossFadeFrames) {
            double factor = frame * frameScaler;
            double value = (1.0 - factor) * source.readDouble(index + sourceStartIndex);
            value += (factor * target.readDouble(index + targetStartIndex));
            return value;
        } else {
            return target.readDouble(index + targetStartIndex);
        }
    }

    @Override
    public double getRateScaler(int index, double synthesisRate) {
        return target.getRateScaler(index, synthesisRate);
    }

    @Override
    public int getChannelsPerFrame() {
        return target.getChannelsPerFrame();
    }

}