From 46888fae6eb7b1dd386f7af7d101ead99ae61981 Mon Sep 17 00:00:00 2001 From: RubbaBoy Date: Mon, 6 Jul 2020 02:33:28 -0400 Subject: Restructured project, added gradle, JUnit, logger, and more Added Gradle (and removed ant), modernized testing via the JUnit framework, moved standalone examples from the tests directory to a separate module, removed sparsely used Java logger and replaced it with SLF4J. More work could be done, however this is a great start to greatly improving the health of the codebase. --- .classpath | 9 - .gitignore | 14 +- .project | 17 - DEVELOPING.md | 44 ++ README.txt | 1 - android/README.md | 2 + .../jsyn/devices/android/AndroidAudioForJSyn.java | 4 +- build.gradle | 60 ++ build.xml | 66 -- examples/build.gradle | 14 + .../java/com/jsyn/examples/AudioPassThrough.java | 74 +++ .../main/java/com/jsyn/examples/ChebyshevSong.java | 185 ++++++ .../main/java/com/jsyn/examples/CircuitTester.java | 111 ++++ .../java/com/jsyn/examples/CustomCubeUnit.java | 48 ++ .../java/com/jsyn/examples/DualOscilloscope.java | 163 +++++ .../main/java/com/jsyn/examples/EditEnvelope1.java | 148 +++++ .../java/com/jsyn/examples/FFTPassthrough.java | 105 ++++ .../com/jsyn/examples/GoogleWaveOscillator.java | 72 +++ .../main/java/com/jsyn/examples/HearDAHDSR.java | 129 ++++ .../java/com/jsyn/examples/HearMoogFilter.java | 207 ++++++ .../main/java/com/jsyn/examples/HearSinePM.java | 128 ++++ .../java/com/jsyn/examples/HearSpectralFilter.java | 211 +++++++ .../java/com/jsyn/examples/ListAudioDevices.java | 50 ++ .../src/main/java/com/jsyn/examples/LongEcho.java | 128 ++++ .../java/com/jsyn/examples/MonoPassThrough.java | 66 ++ .../main/java/com/jsyn/examples/NotesToTone.java | 219 +++++++ .../main/java/com/jsyn/examples/PlayChords.java | 178 ++++++ .../java/com/jsyn/examples/PlayCustomUnit.java | 73 +++ .../main/java/com/jsyn/examples/PlayFunction.java | 90 +++ .../main/java/com/jsyn/examples/PlayGrains.java | 211 +++++++ .../src/main/java/com/jsyn/examples/PlayMIDI.java | 233 +++++++ .../src/main/java/com/jsyn/examples/PlayNotes.java | 102 +++ .../main/java/com/jsyn/examples/PlayPartials.java | 115 ++++ .../main/java/com/jsyn/examples/PlaySample.java | 126 ++++ .../com/jsyn/examples/PlaySampleCrossfade.java | 188 ++++++ .../com/jsyn/examples/PlaySampleWaveShaper.java | 116 ++++ .../com/jsyn/examples/PlaySegmentedEnvelope.java | 156 +++++ .../examples/PlaySegmentedEnvelopeCallback.java | 119 ++++ .../main/java/com/jsyn/examples/PlaySequence.java | 85 +++ .../src/main/java/com/jsyn/examples/PlayTone.java | 81 +++ .../java/com/jsyn/examples/RecordSineSweep.java | 141 +++++ .../com/jsyn/examples/SampleHoldNoteBlaster.java | 153 +++++ .../src/main/java/com/jsyn/examples/SawFaders.java | 103 +++ .../main/java/com/jsyn/examples/SeeGoogleWave.java | 110 ++++ .../java/com/jsyn/examples/SeeOscillators.java | 219 +++++++ .../src/main/java/com/jsyn/examples/ShowWaves.java | 120 ++++ .../java/com/jsyn/examples/SwarmOfOscillators.java | 146 +++++ .../java/com/jsyn/examples/UseMidiKeyboard.java | 125 ++++ .../main/java/com/jsyn/examples/WindCircuit.java | 90 +++ examples/src/main/resources/log4j.xml | 15 + gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 55616 bytes gradle/wrapper/gradle-wrapper.properties | 5 + gradlew | 188 ++++++ gradlew.bat | 100 +++ settings.gradle | 2 + src/com/jsyn/JSyn.java | 78 --- src/com/jsyn/Synthesizer.java | 202 ------ src/com/jsyn/apps/AboutJSyn.java | 114 ---- src/com/jsyn/apps/InstrumentTester.java | 205 ------ src/com/jsyn/data/AudioSample.java | 108 ---- src/com/jsyn/data/DoubleTable.java | 109 ---- src/com/jsyn/data/FloatSample.java | 164 ----- src/com/jsyn/data/Function.java | 35 -- src/com/jsyn/data/HammingWindow.java | 41 -- src/com/jsyn/data/HannWindow.java | 36 -- src/com/jsyn/data/SampleMarker.java | 30 - src/com/jsyn/data/SegmentedEnvelope.java | 125 ---- src/com/jsyn/data/SequentialData.java | 97 --- src/com/jsyn/data/SequentialDataCommon.java | 136 ---- src/com/jsyn/data/ShortSample.java | 123 ---- src/com/jsyn/data/SpectralWindow.java | 21 - src/com/jsyn/data/SpectralWindowFactory.java | 55 -- src/com/jsyn/data/Spectrum.java | 97 --- src/com/jsyn/devices/AudioDeviceFactory.java | 93 --- src/com/jsyn/devices/AudioDeviceInputStream.java | 31 - src/com/jsyn/devices/AudioDeviceManager.java | 120 ---- src/com/jsyn/devices/AudioDeviceOutputStream.java | 30 - .../devices/javasound/JavaSoundAudioDevice.java | 431 ------------- .../jsyn/devices/javasound/MidiDeviceTools.java | 80 --- .../jsyn/devices/jportaudio/JPortAudioDevice.java | 259 -------- src/com/jsyn/engine/LoadAnalyzer.java | 61 -- src/com/jsyn/engine/MultiTable.java | 230 ------- src/com/jsyn/engine/SynthesisEngine.java | 698 -------------------- .../jsyn/exceptions/ChannelMismatchException.java | 35 -- src/com/jsyn/instruments/DrumWoodFM.java | 159 ----- .../jsyn/instruments/DualOscillatorSynthVoice.java | 301 --------- .../jsyn/instruments/JSynInstrumentLibrary.java | 48 -- src/com/jsyn/instruments/NoiseHit.java | 114 ---- .../jsyn/instruments/SubtractiveSynthVoice.java | 182 ------ src/com/jsyn/instruments/WaveShapingVoice.java | 187 ------ src/com/jsyn/io/AudioFifo.java | 204 ------ src/com/jsyn/io/AudioInputStream.java | 46 -- src/com/jsyn/io/AudioOutputStream.java | 29 - src/com/jsyn/midi/MessageParser.java | 147 ----- src/com/jsyn/midi/MidiConstants.java | 84 --- src/com/jsyn/midi/MidiSynthesizer.java | 118 ---- src/com/jsyn/package.html | 17 - src/com/jsyn/ports/ConnectableInput.java | 38 -- src/com/jsyn/ports/ConnectableOutput.java | 23 - src/com/jsyn/ports/GettablePort.java | 27 - src/com/jsyn/ports/InputMixingBlockPart.java | 112 ---- src/com/jsyn/ports/PortBlockPart.java | 205 ------ src/com/jsyn/ports/QueueDataCommand.java | 170 ----- src/com/jsyn/ports/QueueDataEvent.java | 80 --- src/com/jsyn/ports/SequentialDataCrossfade.java | 139 ---- src/com/jsyn/ports/SettablePort.java | 28 - src/com/jsyn/ports/UnitBlockPort.java | 110 ---- src/com/jsyn/ports/UnitDataQueueCallback.java | 31 - src/com/jsyn/ports/UnitDataQueuePort.java | 466 -------------- src/com/jsyn/ports/UnitFunctionPort.java | 48 -- src/com/jsyn/ports/UnitGatePort.java | 158 ----- src/com/jsyn/ports/UnitInputPort.java | 254 -------- src/com/jsyn/ports/UnitOutputPort.java | 103 --- src/com/jsyn/ports/UnitPort.java | 85 --- src/com/jsyn/ports/UnitSpectralInputPort.java | 83 --- src/com/jsyn/ports/UnitSpectralOutputPort.java | 69 -- src/com/jsyn/ports/UnitVariablePort.java | 64 -- src/com/jsyn/ports/package.html | 13 - src/com/jsyn/scope/AudioScope.java | 101 --- src/com/jsyn/scope/AudioScopeModel.java | 157 ----- src/com/jsyn/scope/AudioScopeProbe.java | 94 --- src/com/jsyn/scope/DefaultWaveTraceModel.java | 48 -- src/com/jsyn/scope/MultiChannelScopeProbeUnit.java | 246 -------- src/com/jsyn/scope/TriggerModel.java | 67 -- src/com/jsyn/scope/WaveTraceModel.java | 27 - src/com/jsyn/scope/swing/AudioScopeProbeView.java | 45 -- src/com/jsyn/scope/swing/AudioScopeView.java | 112 ---- src/com/jsyn/scope/swing/MultipleWaveDisplay.java | 58 -- src/com/jsyn/scope/swing/ScopeControlPanel.java | 46 -- src/com/jsyn/scope/swing/ScopeProbePanel.java | 82 --- src/com/jsyn/scope/swing/ScopeTriggerPanel.java | 47 -- src/com/jsyn/scope/swing/WaveTraceView.java | 122 ---- src/com/jsyn/swing/ASCIIMusicKeyboard.java | 193 ------ src/com/jsyn/swing/DoubleBoundedRangeModel.java | 86 --- src/com/jsyn/swing/DoubleBoundedRangeSlider.java | 101 --- src/com/jsyn/swing/DoubleBoundedTextField.java | 94 --- src/com/jsyn/swing/EnvelopeEditorBox.java | 573 ----------------- src/com/jsyn/swing/EnvelopeEditorPanel.java | 164 ----- src/com/jsyn/swing/EnvelopePoints.java | 234 ------- src/com/jsyn/swing/ExponentialRangeModel.java | 105 ---- src/com/jsyn/swing/InstrumentBrowser.java | 117 ---- src/com/jsyn/swing/JAppletFrame.java | 65 -- src/com/jsyn/swing/PortBoundedRangeModel.java | 45 -- src/com/jsyn/swing/PortControllerFactory.java | 60 -- src/com/jsyn/swing/PortModelFactory.java | 64 -- src/com/jsyn/swing/PresetSelectionListener.java | 23 - src/com/jsyn/swing/RotaryController.java | 335 ---------- src/com/jsyn/swing/RotaryTextController.java | 53 -- src/com/jsyn/swing/SoundTweaker.java | 122 ---- src/com/jsyn/swing/XYController.java | 132 ---- src/com/jsyn/unitgen/Add.java | 50 -- src/com/jsyn/unitgen/AsymptoticRamp.java | 81 --- src/com/jsyn/unitgen/BrownNoise.java | 75 --- src/com/jsyn/unitgen/ChannelIn.java | 59 -- src/com/jsyn/unitgen/ChannelOut.java | 62 -- src/com/jsyn/unitgen/Circuit.java | 122 ---- src/com/jsyn/unitgen/Compare.java | 38 -- src/com/jsyn/unitgen/ContinuousRamp.java | 91 --- src/com/jsyn/unitgen/CrossFade.java | 60 -- src/com/jsyn/unitgen/Delay.java | 57 -- src/com/jsyn/unitgen/Divide.java | 53 -- src/com/jsyn/unitgen/DualInTwoOut.java | 59 -- src/com/jsyn/unitgen/EdgeDetector.java | 44 -- src/com/jsyn/unitgen/EnvelopeAttackDecay.java | 145 ----- src/com/jsyn/unitgen/EnvelopeDAHDSR.java | 294 --------- src/com/jsyn/unitgen/ExponentialRamp.java | 104 --- src/com/jsyn/unitgen/FFT.java | 36 -- src/com/jsyn/unitgen/FFTBase.java | 86 --- src/com/jsyn/unitgen/FilterAllPass.java | 62 -- src/com/jsyn/unitgen/FilterBandPass.java | 44 -- src/com/jsyn/unitgen/FilterBandStop.java | 49 -- src/com/jsyn/unitgen/FilterBiquad.java | 156 ----- src/com/jsyn/unitgen/FilterBiquadCommon.java | 99 --- src/com/jsyn/unitgen/FilterBiquadShelf.java | 111 ---- src/com/jsyn/unitgen/FilterFourPoles.java | 185 ------ src/com/jsyn/unitgen/FilterHighPass.java | 46 -- src/com/jsyn/unitgen/FilterHighShelf.java | 38 -- src/com/jsyn/unitgen/FilterLowPass.java | 65 -- src/com/jsyn/unitgen/FilterLowShelf.java | 40 -- src/com/jsyn/unitgen/FilterOnePole.java | 62 -- src/com/jsyn/unitgen/FilterOnePoleOneZero.java | 68 -- src/com/jsyn/unitgen/FilterOneZero.java | 65 -- src/com/jsyn/unitgen/FilterPeakingEQ.java | 68 -- src/com/jsyn/unitgen/FilterStateVariable.java | 120 ---- src/com/jsyn/unitgen/FilterTwoPoles.java | 66 -- src/com/jsyn/unitgen/FilterTwoPolesTwoZeros.java | 79 --- src/com/jsyn/unitgen/FixedRateMonoReader.java | 52 -- src/com/jsyn/unitgen/FixedRateMonoWriter.java | 54 -- src/com/jsyn/unitgen/FixedRateStereoReader.java | 59 -- src/com/jsyn/unitgen/FixedRateStereoWriter.java | 60 -- src/com/jsyn/unitgen/FourWayFade.java | 94 --- src/com/jsyn/unitgen/FunctionEvaluator.java | 76 --- src/com/jsyn/unitgen/FunctionOscillator.java | 58 -- src/com/jsyn/unitgen/Grain.java | 89 --- src/com/jsyn/unitgen/GrainCommon.java | 32 - src/com/jsyn/unitgen/GrainEnvelope.java | 52 -- src/com/jsyn/unitgen/GrainFarm.java | 178 ------ src/com/jsyn/unitgen/GrainScheduler.java | 44 -- src/com/jsyn/unitgen/GrainSource.java | 36 -- src/com/jsyn/unitgen/GrainSourceSine.java | 51 -- src/com/jsyn/unitgen/IFFT.java | 36 -- src/com/jsyn/unitgen/ImpulseOscillator.java | 59 -- src/com/jsyn/unitgen/ImpulseOscillatorBL.java | 39 -- src/com/jsyn/unitgen/Integrate.java | 82 --- src/com/jsyn/unitgen/InterpolatingDelay.java | 117 ---- src/com/jsyn/unitgen/Latch.java | 53 -- src/com/jsyn/unitgen/LatchZeroCrossing.java | 72 --- src/com/jsyn/unitgen/LineIn.java | 51 -- src/com/jsyn/unitgen/LineOut.java | 57 -- src/com/jsyn/unitgen/LinearRamp.java | 94 --- src/com/jsyn/unitgen/Maximum.java | 42 -- src/com/jsyn/unitgen/Minimum.java | 43 -- src/com/jsyn/unitgen/MixerMono.java | 77 --- src/com/jsyn/unitgen/MixerMonoRamped.java | 54 -- src/com/jsyn/unitgen/MixerStereo.java | 94 --- src/com/jsyn/unitgen/MixerStereoRamped.java | 71 --- src/com/jsyn/unitgen/MonoStreamWriter.java | 49 -- src/com/jsyn/unitgen/MorphingOscillatorBL.java | 72 --- src/com/jsyn/unitgen/MultiPassThrough.java | 70 --- src/com/jsyn/unitgen/Multiply.java | 64 -- src/com/jsyn/unitgen/MultiplyAdd.java | 57 -- src/com/jsyn/unitgen/Pan.java | 64 -- src/com/jsyn/unitgen/PanControl.java | 61 -- src/com/jsyn/unitgen/ParabolicEnvelope.java | 110 ---- src/com/jsyn/unitgen/PassThrough.java | 38 -- src/com/jsyn/unitgen/PeakFollower.java | 87 --- src/com/jsyn/unitgen/PhaseShifter.java | 90 --- src/com/jsyn/unitgen/PinkNoise.java | 128 ---- src/com/jsyn/unitgen/PitchDetector.java | 115 ---- src/com/jsyn/unitgen/PitchToFrequency.java | 26 - src/com/jsyn/unitgen/PowerOfTwo.java | 108 ---- src/com/jsyn/unitgen/PulseOscillator.java | 59 -- src/com/jsyn/unitgen/PulseOscillatorBL.java | 61 -- src/com/jsyn/unitgen/RaisedCosineEnvelope.java | 73 --- src/com/jsyn/unitgen/RangeConverter.java | 50 -- src/com/jsyn/unitgen/RectangularWindow.java | 39 -- src/com/jsyn/unitgen/RedNoise.java | 80 --- src/com/jsyn/unitgen/SampleGrainFarm.java | 71 --- src/com/jsyn/unitgen/SampleGrainSource.java | 69 -- src/com/jsyn/unitgen/SawtoothOscillator.java | 47 -- src/com/jsyn/unitgen/SawtoothOscillatorBL.java | 65 -- src/com/jsyn/unitgen/SawtoothOscillatorDPW.java | 76 --- src/com/jsyn/unitgen/SchmidtTrigger.java | 83 --- src/com/jsyn/unitgen/Select.java | 56 -- src/com/jsyn/unitgen/SequentialDataReader.java | 38 -- src/com/jsyn/unitgen/SequentialDataWriter.java | 44 -- src/com/jsyn/unitgen/SineOscillator.java | 85 --- .../jsyn/unitgen/SineOscillatorPhaseModulated.java | 74 --- src/com/jsyn/unitgen/SpectralFFT.java | 130 ---- src/com/jsyn/unitgen/SpectralFilter.java | 130 ---- src/com/jsyn/unitgen/SpectralIFFT.java | 92 --- src/com/jsyn/unitgen/SpectralProcessor.java | 73 --- src/com/jsyn/unitgen/SquareOscillator.java | 49 -- src/com/jsyn/unitgen/SquareOscillatorBL.java | 48 -- src/com/jsyn/unitgen/StereoStreamWriter.java | 53 -- src/com/jsyn/unitgen/StochasticGrainScheduler.java | 43 -- src/com/jsyn/unitgen/Subtract.java | 42 -- src/com/jsyn/unitgen/TriangleOscillator.java | 59 -- src/com/jsyn/unitgen/TunableFilter.java | 41 -- src/com/jsyn/unitgen/TwoInDualOut.java | 56 -- src/com/jsyn/unitgen/UnitBinaryOperator.java | 41 -- src/com/jsyn/unitgen/UnitFilter.java | 47 -- src/com/jsyn/unitgen/UnitGate.java | 54 -- src/com/jsyn/unitgen/UnitGenerator.java | 355 ----------- src/com/jsyn/unitgen/UnitOscillator.java | 93 --- src/com/jsyn/unitgen/UnitSink.java | 43 -- src/com/jsyn/unitgen/UnitSource.java | 30 - src/com/jsyn/unitgen/UnitStreamWriter.java | 53 -- src/com/jsyn/unitgen/UnitVoice.java | 59 -- src/com/jsyn/unitgen/Unzipper.java | 47 -- src/com/jsyn/unitgen/VariableRateDataReader.java | 29 - src/com/jsyn/unitgen/VariableRateMonoReader.java | 115 ---- src/com/jsyn/unitgen/VariableRateStereoReader.java | 113 ---- src/com/jsyn/unitgen/WhiteNoise.java | 56 -- src/com/jsyn/unitgen/ZeroCrossingCounter.java | 61 -- src/com/jsyn/util/AudioSampleLoader.java | 42 -- src/com/jsyn/util/AudioStreamReader.java | 85 --- src/com/jsyn/util/AutoCorrelator.java | 293 --------- src/com/jsyn/util/Instrument.java | 38 -- src/com/jsyn/util/InstrumentLibrary.java | 32 - src/com/jsyn/util/JavaSoundSampleLoader.java | 149 ----- src/com/jsyn/util/JavaTools.java | 59 -- src/com/jsyn/util/MultiChannelSynthesizer.java | 404 ------------ src/com/jsyn/util/NumericOutput.java | 187 ------ src/com/jsyn/util/PolyphonicInstrument.java | 155 ----- src/com/jsyn/util/PseudoRandom.java | 89 --- src/com/jsyn/util/RecursiveSequenceGenerator.java | 214 ------- src/com/jsyn/util/SampleLoader.java | 230 ------- src/com/jsyn/util/SignalCorrelator.java | 48 -- src/com/jsyn/util/StreamingThread.java | 121 ---- src/com/jsyn/util/TransportListener.java | 31 - src/com/jsyn/util/TransportModel.java | 67 -- src/com/jsyn/util/VoiceAllocator.java | 258 -------- src/com/jsyn/util/VoiceDescription.java | 68 -- src/com/jsyn/util/VoiceOperation.java | 7 - src/com/jsyn/util/WaveFileWriter.java | 293 --------- src/com/jsyn/util/WaveRecorder.java | 134 ---- src/com/jsyn/util/soundfile/AIFFFileParser.java | 227 ------- src/com/jsyn/util/soundfile/AudioFileParser.java | 129 ---- src/com/jsyn/util/soundfile/ChunkHandler.java | 49 -- .../jsyn/util/soundfile/CustomSampleLoader.java | 60 -- src/com/jsyn/util/soundfile/IFFParser.java | 307 --------- src/com/jsyn/util/soundfile/WAVEFileParser.java | 334 ---------- src/com/softsynth/math/AudioMath.java | 84 --- src/com/softsynth/math/ChebyshevPolynomial.java | 45 -- src/com/softsynth/math/FourierMath.java | 254 -------- src/com/softsynth/math/JustRatio.java | 47 -- src/com/softsynth/math/Polynomial.java | 253 -------- src/com/softsynth/math/PolynomialTableData.java | 64 -- src/com/softsynth/math/PrimeFactors.java | 244 ------- .../softsynth/shared/time/ScheduledCommand.java | 21 - src/com/softsynth/shared/time/ScheduledQueue.java | 85 --- src/com/softsynth/shared/time/TimeStamp.java | 56 -- src/main/java/com/jsyn/JSyn.java | 78 +++ src/main/java/com/jsyn/Synthesizer.java | 202 ++++++ src/main/java/com/jsyn/apps/AboutJSyn.java | 114 ++++ src/main/java/com/jsyn/apps/InstrumentTester.java | 210 +++++++ src/main/java/com/jsyn/data/AudioSample.java | 108 ++++ src/main/java/com/jsyn/data/DoubleTable.java | 109 ++++ src/main/java/com/jsyn/data/FloatSample.java | 164 +++++ src/main/java/com/jsyn/data/Function.java | 35 ++ src/main/java/com/jsyn/data/HammingWindow.java | 41 ++ src/main/java/com/jsyn/data/HannWindow.java | 36 ++ src/main/java/com/jsyn/data/SampleMarker.java | 30 + src/main/java/com/jsyn/data/SegmentedEnvelope.java | 125 ++++ src/main/java/com/jsyn/data/SequentialData.java | 96 +++ .../java/com/jsyn/data/SequentialDataCommon.java | 136 ++++ src/main/java/com/jsyn/data/ShortSample.java | 123 ++++ src/main/java/com/jsyn/data/SpectralWindow.java | 21 + .../java/com/jsyn/data/SpectralWindowFactory.java | 55 ++ src/main/java/com/jsyn/data/Spectrum.java | 97 +++ .../java/com/jsyn/devices/AudioDeviceFactory.java | 93 +++ .../com/jsyn/devices/AudioDeviceInputStream.java | 31 + .../java/com/jsyn/devices/AudioDeviceManager.java | 120 ++++ .../com/jsyn/devices/AudioDeviceOutputStream.java | 30 + .../devices/javasound/JavaSoundAudioDevice.java | 432 +++++++++++++ .../jsyn/devices/javasound/MidiDeviceTools.java | 86 +++ .../jsyn/devices/jportaudio/JPortAudioDevice.java | 264 ++++++++ src/main/java/com/jsyn/engine/LoadAnalyzer.java | 61 ++ src/main/java/com/jsyn/engine/MultiTable.java | 230 +++++++ src/main/java/com/jsyn/engine/SynthesisEngine.java | 700 +++++++++++++++++++++ .../jsyn/exceptions/ChannelMismatchException.java | 35 ++ src/main/java/com/jsyn/instruments/DrumWoodFM.java | 159 +++++ .../jsyn/instruments/DualOscillatorSynthVoice.java | 301 +++++++++ .../jsyn/instruments/JSynInstrumentLibrary.java | 48 ++ src/main/java/com/jsyn/instruments/NoiseHit.java | 114 ++++ .../jsyn/instruments/SubtractiveSynthVoice.java | 182 ++++++ .../com/jsyn/instruments/WaveShapingVoice.java | 187 ++++++ src/main/java/com/jsyn/io/AudioFifo.java | 204 ++++++ src/main/java/com/jsyn/io/AudioInputStream.java | 46 ++ src/main/java/com/jsyn/io/AudioOutputStream.java | 29 + src/main/java/com/jsyn/midi/MessageParser.java | 147 +++++ src/main/java/com/jsyn/midi/MidiConstants.java | 84 +++ src/main/java/com/jsyn/midi/MidiSynthesizer.java | 121 ++++ src/main/java/com/jsyn/package.html | 17 + src/main/java/com/jsyn/ports/ConnectableInput.java | 38 ++ .../java/com/jsyn/ports/ConnectableOutput.java | 23 + src/main/java/com/jsyn/ports/GettablePort.java | 27 + .../java/com/jsyn/ports/InputMixingBlockPart.java | 112 ++++ src/main/java/com/jsyn/ports/PortBlockPart.java | 210 +++++++ src/main/java/com/jsyn/ports/QueueDataCommand.java | 170 +++++ src/main/java/com/jsyn/ports/QueueDataEvent.java | 80 +++ .../com/jsyn/ports/SequentialDataCrossfade.java | 139 ++++ src/main/java/com/jsyn/ports/SettablePort.java | 28 + src/main/java/com/jsyn/ports/UnitBlockPort.java | 110 ++++ .../java/com/jsyn/ports/UnitDataQueueCallback.java | 31 + .../java/com/jsyn/ports/UnitDataQueuePort.java | 466 ++++++++++++++ src/main/java/com/jsyn/ports/UnitFunctionPort.java | 48 ++ src/main/java/com/jsyn/ports/UnitGatePort.java | 158 +++++ src/main/java/com/jsyn/ports/UnitInputPort.java | 254 ++++++++ src/main/java/com/jsyn/ports/UnitOutputPort.java | 103 +++ src/main/java/com/jsyn/ports/UnitPort.java | 85 +++ .../java/com/jsyn/ports/UnitSpectralInputPort.java | 83 +++ .../com/jsyn/ports/UnitSpectralOutputPort.java | 69 ++ src/main/java/com/jsyn/ports/UnitVariablePort.java | 64 ++ src/main/java/com/jsyn/ports/package.html | 13 + src/main/java/com/jsyn/scope/AudioScope.java | 101 +++ src/main/java/com/jsyn/scope/AudioScopeModel.java | 157 +++++ src/main/java/com/jsyn/scope/AudioScopeProbe.java | 94 +++ .../java/com/jsyn/scope/DefaultWaveTraceModel.java | 48 ++ .../com/jsyn/scope/MultiChannelScopeProbeUnit.java | 246 ++++++++ src/main/java/com/jsyn/scope/TriggerModel.java | 67 ++ src/main/java/com/jsyn/scope/WaveTraceModel.java | 27 + .../com/jsyn/scope/swing/AudioScopeProbeView.java | 45 ++ .../java/com/jsyn/scope/swing/AudioScopeView.java | 112 ++++ .../com/jsyn/scope/swing/MultipleWaveDisplay.java | 58 ++ .../com/jsyn/scope/swing/ScopeControlPanel.java | 46 ++ .../java/com/jsyn/scope/swing/ScopeProbePanel.java | 87 +++ .../com/jsyn/scope/swing/ScopeTriggerPanel.java | 47 ++ .../java/com/jsyn/scope/swing/WaveTraceView.java | 122 ++++ .../java/com/jsyn/swing/ASCIIMusicKeyboard.java | 199 ++++++ .../com/jsyn/swing/DoubleBoundedRangeModel.java | 86 +++ .../com/jsyn/swing/DoubleBoundedRangeSlider.java | 101 +++ .../com/jsyn/swing/DoubleBoundedTextField.java | 94 +++ .../java/com/jsyn/swing/EnvelopeEditorBox.java | 573 +++++++++++++++++ .../java/com/jsyn/swing/EnvelopeEditorPanel.java | 164 +++++ src/main/java/com/jsyn/swing/EnvelopePoints.java | 234 +++++++ .../java/com/jsyn/swing/ExponentialRangeModel.java | 110 ++++ .../java/com/jsyn/swing/InstrumentBrowser.java | 117 ++++ src/main/java/com/jsyn/swing/JAppletFrame.java | 65 ++ .../java/com/jsyn/swing/PortBoundedRangeModel.java | 45 ++ .../java/com/jsyn/swing/PortControllerFactory.java | 60 ++ src/main/java/com/jsyn/swing/PortModelFactory.java | 64 ++ .../com/jsyn/swing/PresetSelectionListener.java | 23 + src/main/java/com/jsyn/swing/RotaryController.java | 335 ++++++++++ .../java/com/jsyn/swing/RotaryTextController.java | 53 ++ src/main/java/com/jsyn/swing/SoundTweaker.java | 120 ++++ src/main/java/com/jsyn/swing/XYController.java | 132 ++++ src/main/java/com/jsyn/unitgen/Add.java | 50 ++ src/main/java/com/jsyn/unitgen/AsymptoticRamp.java | 81 +++ src/main/java/com/jsyn/unitgen/BrownNoise.java | 75 +++ src/main/java/com/jsyn/unitgen/ChannelIn.java | 59 ++ src/main/java/com/jsyn/unitgen/ChannelOut.java | 62 ++ src/main/java/com/jsyn/unitgen/Circuit.java | 122 ++++ src/main/java/com/jsyn/unitgen/Compare.java | 38 ++ src/main/java/com/jsyn/unitgen/ContinuousRamp.java | 91 +++ src/main/java/com/jsyn/unitgen/CrossFade.java | 60 ++ src/main/java/com/jsyn/unitgen/Delay.java | 57 ++ src/main/java/com/jsyn/unitgen/Divide.java | 53 ++ src/main/java/com/jsyn/unitgen/DualInTwoOut.java | 59 ++ src/main/java/com/jsyn/unitgen/EdgeDetector.java | 44 ++ .../java/com/jsyn/unitgen/EnvelopeAttackDecay.java | 145 +++++ src/main/java/com/jsyn/unitgen/EnvelopeDAHDSR.java | 294 +++++++++ .../java/com/jsyn/unitgen/ExponentialRamp.java | 104 +++ src/main/java/com/jsyn/unitgen/FFT.java | 36 ++ src/main/java/com/jsyn/unitgen/FFTBase.java | 86 +++ src/main/java/com/jsyn/unitgen/FilterAllPass.java | 62 ++ src/main/java/com/jsyn/unitgen/FilterBandPass.java | 44 ++ src/main/java/com/jsyn/unitgen/FilterBandStop.java | 49 ++ src/main/java/com/jsyn/unitgen/FilterBiquad.java | 156 +++++ .../java/com/jsyn/unitgen/FilterBiquadCommon.java | 99 +++ .../java/com/jsyn/unitgen/FilterBiquadShelf.java | 111 ++++ .../java/com/jsyn/unitgen/FilterFourPoles.java | 185 ++++++ src/main/java/com/jsyn/unitgen/FilterHighPass.java | 46 ++ .../java/com/jsyn/unitgen/FilterHighShelf.java | 38 ++ src/main/java/com/jsyn/unitgen/FilterLowPass.java | 65 ++ src/main/java/com/jsyn/unitgen/FilterLowShelf.java | 40 ++ src/main/java/com/jsyn/unitgen/FilterOnePole.java | 62 ++ .../com/jsyn/unitgen/FilterOnePoleOneZero.java | 68 ++ src/main/java/com/jsyn/unitgen/FilterOneZero.java | 65 ++ .../java/com/jsyn/unitgen/FilterPeakingEQ.java | 68 ++ .../java/com/jsyn/unitgen/FilterStateVariable.java | 120 ++++ src/main/java/com/jsyn/unitgen/FilterTwoPoles.java | 66 ++ .../com/jsyn/unitgen/FilterTwoPolesTwoZeros.java | 79 +++ .../java/com/jsyn/unitgen/FixedRateMonoReader.java | 52 ++ .../java/com/jsyn/unitgen/FixedRateMonoWriter.java | 54 ++ .../com/jsyn/unitgen/FixedRateStereoReader.java | 59 ++ .../com/jsyn/unitgen/FixedRateStereoWriter.java | 60 ++ src/main/java/com/jsyn/unitgen/FourWayFade.java | 94 +++ .../java/com/jsyn/unitgen/FunctionEvaluator.java | 76 +++ .../java/com/jsyn/unitgen/FunctionOscillator.java | 58 ++ src/main/java/com/jsyn/unitgen/Grain.java | 89 +++ src/main/java/com/jsyn/unitgen/GrainCommon.java | 32 + src/main/java/com/jsyn/unitgen/GrainEnvelope.java | 52 ++ src/main/java/com/jsyn/unitgen/GrainFarm.java | 178 ++++++ src/main/java/com/jsyn/unitgen/GrainScheduler.java | 44 ++ src/main/java/com/jsyn/unitgen/GrainSource.java | 36 ++ .../java/com/jsyn/unitgen/GrainSourceSine.java | 51 ++ src/main/java/com/jsyn/unitgen/IFFT.java | 36 ++ .../java/com/jsyn/unitgen/ImpulseOscillator.java | 59 ++ .../java/com/jsyn/unitgen/ImpulseOscillatorBL.java | 39 ++ src/main/java/com/jsyn/unitgen/Integrate.java | 82 +++ .../java/com/jsyn/unitgen/InterpolatingDelay.java | 117 ++++ src/main/java/com/jsyn/unitgen/Latch.java | 53 ++ .../java/com/jsyn/unitgen/LatchZeroCrossing.java | 72 +++ src/main/java/com/jsyn/unitgen/LineIn.java | 51 ++ src/main/java/com/jsyn/unitgen/LineOut.java | 57 ++ src/main/java/com/jsyn/unitgen/LinearRamp.java | 94 +++ src/main/java/com/jsyn/unitgen/Maximum.java | 42 ++ src/main/java/com/jsyn/unitgen/Minimum.java | 43 ++ src/main/java/com/jsyn/unitgen/MixerMono.java | 77 +++ .../java/com/jsyn/unitgen/MixerMonoRamped.java | 54 ++ src/main/java/com/jsyn/unitgen/MixerStereo.java | 94 +++ .../java/com/jsyn/unitgen/MixerStereoRamped.java | 71 +++ .../java/com/jsyn/unitgen/MonoStreamWriter.java | 49 ++ .../com/jsyn/unitgen/MorphingOscillatorBL.java | 72 +++ .../java/com/jsyn/unitgen/MultiPassThrough.java | 70 +++ src/main/java/com/jsyn/unitgen/Multiply.java | 64 ++ src/main/java/com/jsyn/unitgen/MultiplyAdd.java | 57 ++ src/main/java/com/jsyn/unitgen/Pan.java | 64 ++ src/main/java/com/jsyn/unitgen/PanControl.java | 61 ++ .../java/com/jsyn/unitgen/ParabolicEnvelope.java | 110 ++++ src/main/java/com/jsyn/unitgen/PassThrough.java | 38 ++ src/main/java/com/jsyn/unitgen/PeakFollower.java | 87 +++ src/main/java/com/jsyn/unitgen/PhaseShifter.java | 90 +++ src/main/java/com/jsyn/unitgen/PinkNoise.java | 128 ++++ src/main/java/com/jsyn/unitgen/PitchDetector.java | 120 ++++ .../java/com/jsyn/unitgen/PitchToFrequency.java | 26 + src/main/java/com/jsyn/unitgen/PowerOfTwo.java | 108 ++++ .../java/com/jsyn/unitgen/PulseOscillator.java | 59 ++ .../java/com/jsyn/unitgen/PulseOscillatorBL.java | 61 ++ .../com/jsyn/unitgen/RaisedCosineEnvelope.java | 73 +++ src/main/java/com/jsyn/unitgen/RangeConverter.java | 50 ++ .../java/com/jsyn/unitgen/RectangularWindow.java | 39 ++ src/main/java/com/jsyn/unitgen/RedNoise.java | 80 +++ .../java/com/jsyn/unitgen/SampleGrainFarm.java | 71 +++ .../java/com/jsyn/unitgen/SampleGrainSource.java | 69 ++ .../java/com/jsyn/unitgen/SawtoothOscillator.java | 47 ++ .../com/jsyn/unitgen/SawtoothOscillatorBL.java | 65 ++ .../com/jsyn/unitgen/SawtoothOscillatorDPW.java | 76 +++ src/main/java/com/jsyn/unitgen/SchmidtTrigger.java | 83 +++ src/main/java/com/jsyn/unitgen/Select.java | 56 ++ .../com/jsyn/unitgen/SequentialDataReader.java | 38 ++ .../com/jsyn/unitgen/SequentialDataWriter.java | 44 ++ src/main/java/com/jsyn/unitgen/SineOscillator.java | 84 +++ .../jsyn/unitgen/SineOscillatorPhaseModulated.java | 74 +++ src/main/java/com/jsyn/unitgen/SpectralFFT.java | 130 ++++ src/main/java/com/jsyn/unitgen/SpectralFilter.java | 130 ++++ src/main/java/com/jsyn/unitgen/SpectralIFFT.java | 92 +++ .../java/com/jsyn/unitgen/SpectralProcessor.java | 73 +++ .../java/com/jsyn/unitgen/SquareOscillator.java | 49 ++ .../java/com/jsyn/unitgen/SquareOscillatorBL.java | 48 ++ .../java/com/jsyn/unitgen/StereoStreamWriter.java | 53 ++ .../com/jsyn/unitgen/StochasticGrainScheduler.java | 43 ++ src/main/java/com/jsyn/unitgen/Subtract.java | 42 ++ .../java/com/jsyn/unitgen/TriangleOscillator.java | 59 ++ src/main/java/com/jsyn/unitgen/TunableFilter.java | 41 ++ src/main/java/com/jsyn/unitgen/TwoInDualOut.java | 56 ++ .../java/com/jsyn/unitgen/UnitBinaryOperator.java | 41 ++ src/main/java/com/jsyn/unitgen/UnitFilter.java | 47 ++ src/main/java/com/jsyn/unitgen/UnitGate.java | 54 ++ src/main/java/com/jsyn/unitgen/UnitGenerator.java | 357 +++++++++++ src/main/java/com/jsyn/unitgen/UnitOscillator.java | 93 +++ src/main/java/com/jsyn/unitgen/UnitSink.java | 43 ++ src/main/java/com/jsyn/unitgen/UnitSource.java | 30 + .../java/com/jsyn/unitgen/UnitStreamWriter.java | 53 ++ src/main/java/com/jsyn/unitgen/UnitVoice.java | 59 ++ src/main/java/com/jsyn/unitgen/Unzipper.java | 47 ++ .../com/jsyn/unitgen/VariableRateDataReader.java | 29 + .../com/jsyn/unitgen/VariableRateMonoReader.java | 115 ++++ .../com/jsyn/unitgen/VariableRateStereoReader.java | 113 ++++ src/main/java/com/jsyn/unitgen/WhiteNoise.java | 56 ++ .../java/com/jsyn/unitgen/ZeroCrossingCounter.java | 61 ++ src/main/java/com/jsyn/util/AudioSampleLoader.java | 42 ++ src/main/java/com/jsyn/util/AudioStreamReader.java | 85 +++ src/main/java/com/jsyn/util/AutoCorrelator.java | 290 +++++++++ src/main/java/com/jsyn/util/Instrument.java | 38 ++ src/main/java/com/jsyn/util/InstrumentLibrary.java | 32 + .../java/com/jsyn/util/JavaSoundSampleLoader.java | 149 +++++ src/main/java/com/jsyn/util/JavaTools.java | 64 ++ .../com/jsyn/util/MultiChannelSynthesizer.java | 404 ++++++++++++ src/main/java/com/jsyn/util/NumericOutput.java | 193 ++++++ .../java/com/jsyn/util/PolyphonicInstrument.java | 155 +++++ src/main/java/com/jsyn/util/PseudoRandom.java | 89 +++ .../com/jsyn/util/RecursiveSequenceGenerator.java | 214 +++++++ src/main/java/com/jsyn/util/SampleLoader.java | 230 +++++++ src/main/java/com/jsyn/util/SignalCorrelator.java | 48 ++ src/main/java/com/jsyn/util/StreamingThread.java | 121 ++++ src/main/java/com/jsyn/util/TransportListener.java | 31 + src/main/java/com/jsyn/util/TransportModel.java | 67 ++ src/main/java/com/jsyn/util/VoiceAllocator.java | 258 ++++++++ src/main/java/com/jsyn/util/VoiceDescription.java | 68 ++ src/main/java/com/jsyn/util/VoiceOperation.java | 7 + src/main/java/com/jsyn/util/WaveFileWriter.java | 293 +++++++++ src/main/java/com/jsyn/util/WaveRecorder.java | 134 ++++ .../com/jsyn/util/soundfile/AIFFFileParser.java | 232 +++++++ .../com/jsyn/util/soundfile/AudioFileParser.java | 129 ++++ .../java/com/jsyn/util/soundfile/ChunkHandler.java | 49 ++ .../jsyn/util/soundfile/CustomSampleLoader.java | 60 ++ .../java/com/jsyn/util/soundfile/IFFParser.java | 313 +++++++++ .../com/jsyn/util/soundfile/WAVEFileParser.java | 338 ++++++++++ src/main/java/com/softsynth/math/AudioMath.java | 82 +++ .../com/softsynth/math/ChebyshevPolynomial.java | 45 ++ src/main/java/com/softsynth/math/FourierMath.java | 254 ++++++++ src/main/java/com/softsynth/math/JustRatio.java | 47 ++ src/main/java/com/softsynth/math/Polynomial.java | 259 ++++++++ .../com/softsynth/math/PolynomialTableData.java | 64 ++ src/main/java/com/softsynth/math/PrimeFactors.java | 244 +++++++ .../softsynth/shared/time/ScheduledCommand.java | 21 + .../com/softsynth/shared/time/ScheduledQueue.java | 85 +++ .../java/com/softsynth/shared/time/TimeStamp.java | 56 ++ src/main/resources/log4j.xml | 15 + src/test/java/com/jsyn/benchmarks/BenchJSyn.java | 228 +++++++ src/test/java/com/jsyn/data/TestShortSample.java | 72 +++ src/test/java/com/jsyn/engine/TestAudioOutput.java | 78 +++ src/test/java/com/jsyn/engine/TestDevices.java | 75 +++ src/test/java/com/jsyn/engine/TestEngine.java | 225 +++++++ src/test/java/com/jsyn/engine/TestFifo.java | 245 ++++++++ .../com/jsyn/engine/TestWaveFileReadWrite.java | 114 ++++ src/test/java/com/jsyn/midi/TestMidiLoop.java | 87 +++ .../java/com/jsyn/ports/TestQueuedDataPort.java | 549 ++++++++++++++++ .../java/com/jsyn/ports/TestSequentialData.java | 55 ++ src/test/java/com/jsyn/ports/TestSet.java | 89 +++ .../com/jsyn/research/BenchMultiThreading.java | 152 +++++ .../java/com/jsyn/research/RecordVariousRamps.java | 193 ++++++ src/test/java/com/jsyn/swing/TestRangeModels.java | 53 ++ .../java/com/jsyn/unitgen/CalibrateMoogFilter.java | 141 +++++ src/test/java/com/jsyn/unitgen/EnablingGate.java | 51 ++ .../java/com/jsyn/unitgen/NonRealTimeTestCase.java | 42 ++ .../java/com/jsyn/unitgen/RecordMoogFilter.java | 158 +++++ .../java/com/jsyn/unitgen/TestConnections.java | 111 ++++ src/test/java/com/jsyn/unitgen/TestDelay.java | 81 +++ src/test/java/com/jsyn/unitgen/TestEnable.java | 81 +++ .../com/jsyn/unitgen/TestEnvelopeAttackDecay.java | 130 ++++ .../java/com/jsyn/unitgen/TestEnvelopeDAHDSR.java | 355 +++++++++++ src/test/java/com/jsyn/unitgen/TestFunction.java | 77 +++ src/test/java/com/jsyn/unitgen/TestMath.java | 420 +++++++++++++ src/test/java/com/jsyn/unitgen/TestRamps.java | 205 ++++++ src/test/java/com/jsyn/unitgen/TestUnitGate.java | 81 +++ src/test/java/com/jsyn/util/DebugSampleLoader.java | 143 +++++ src/test/java/com/jsyn/util/TestFFT.java | 201 ++++++ src/test/java/com/jsyn/util/TestPseudoRandom.java | 76 +++ .../java/com/jsyn/util/TestVoiceAllocator.java | 111 ++++ tests/com/jsyn/SynthTestSuite.java | 76 --- tests/com/jsyn/benchmarks/BenchJSyn.java | 228 ------- tests/com/jsyn/data/TestShortSample.java | 78 --- tests/com/jsyn/engine/TestAudioOutput.java | 86 --- tests/com/jsyn/engine/TestDevices.java | 69 -- tests/com/jsyn/engine/TestEngine.java | 213 ------- tests/com/jsyn/engine/TestFifo.java | 250 -------- tests/com/jsyn/engine/TestWaveFileReadWrite.java | 107 ---- tests/com/jsyn/examples/AudioPassThrough.java | 71 --- tests/com/jsyn/examples/ChebyshevSong.java | 181 ------ tests/com/jsyn/examples/CircuitTester.java | 113 ---- tests/com/jsyn/examples/CustomCubeUnit.java | 48 -- tests/com/jsyn/examples/DualOscilloscope.java | 164 ----- tests/com/jsyn/examples/EditEnvelope1.java | 148 ----- tests/com/jsyn/examples/FFTPassthrough.java | 100 --- tests/com/jsyn/examples/GoogleWaveOscillator.java | 73 --- tests/com/jsyn/examples/HearDAHDSR.java | 125 ---- tests/com/jsyn/examples/HearMoogFilter.java | 208 ------ tests/com/jsyn/examples/HearSinePM.java | 129 ---- tests/com/jsyn/examples/HearSpectralFilter.java | 206 ------ tests/com/jsyn/examples/ListAudioDevices.java | 46 -- tests/com/jsyn/examples/LongEcho.java | 123 ---- tests/com/jsyn/examples/MonoPassThrough.java | 66 -- tests/com/jsyn/examples/NotesToTone.java | 214 ------- tests/com/jsyn/examples/PlayChords.java | 180 ------ tests/com/jsyn/examples/PlayCustomUnit.java | 73 --- tests/com/jsyn/examples/PlayFunction.java | 91 --- tests/com/jsyn/examples/PlayGrains.java | 208 ------ tests/com/jsyn/examples/PlayMIDI.java | 241 ------- tests/com/jsyn/examples/PlayNotes.java | 103 --- tests/com/jsyn/examples/PlayPartials.java | 110 ---- tests/com/jsyn/examples/PlaySample.java | 121 ---- tests/com/jsyn/examples/PlaySampleCrossfade.java | 183 ------ tests/com/jsyn/examples/PlaySampleWaveShaper.java | 112 ---- tests/com/jsyn/examples/PlaySegmentedEnvelope.java | 151 ----- .../examples/PlaySegmentedEnvelopeCallback.java | 114 ---- tests/com/jsyn/examples/PlaySequence.java | 85 --- tests/com/jsyn/examples/PlayTone.java | 80 --- tests/com/jsyn/examples/RecordSineSweep.java | 136 ---- tests/com/jsyn/examples/SampleHoldNoteBlaster.java | 153 ----- tests/com/jsyn/examples/SawFaders.java | 104 --- tests/com/jsyn/examples/SeeGoogleWave.java | 111 ---- tests/com/jsyn/examples/SeeOscillators.java | 220 ------- tests/com/jsyn/examples/ShowWaves.java | 121 ---- tests/com/jsyn/examples/SwarmOfOscillators.java | 146 ----- tests/com/jsyn/examples/UseMidiKeyboard.java | 121 ---- tests/com/jsyn/examples/WindCircuit.java | 90 --- tests/com/jsyn/midi/TestMidiLoop.java | 98 --- tests/com/jsyn/ports/TestQueuedDataPort.java | 525 ---------------- tests/com/jsyn/ports/TestSequentialData.java | 50 -- tests/com/jsyn/ports/TestSet.java | 96 --- tests/com/jsyn/research/BenchMultiThreading.java | 143 ----- tests/com/jsyn/research/RecordVariousRamps.java | 183 ------ tests/com/jsyn/research/lambdas/LambdaUnits.java | 22 - tests/com/jsyn/swing/TestRangeModels.java | 60 -- tests/com/jsyn/unitgen/CalibrateMoogFilter.java | 141 ----- tests/com/jsyn/unitgen/EnablingGate.java | 51 -- tests/com/jsyn/unitgen/NonRealTimeTestCase.java | 48 -- tests/com/jsyn/unitgen/RecordMoogFilter.java | 153 ----- tests/com/jsyn/unitgen/TestConnections.java | 113 ---- tests/com/jsyn/unitgen/TestDelay.java | 73 --- tests/com/jsyn/unitgen/TestEnable.java | 78 --- .../com/jsyn/unitgen/TestEnvelopeAttackDecay.java | 126 ---- tests/com/jsyn/unitgen/TestEnvelopeDAHDSR.java | 339 ---------- tests/com/jsyn/unitgen/TestFunction.java | 78 --- tests/com/jsyn/unitgen/TestMath.java | 413 ------------ tests/com/jsyn/unitgen/TestRamps.java | 196 ------ tests/com/jsyn/unitgen/TestUnitGate.java | 80 --- tests/com/jsyn/util/DebugSampleLoader.java | 138 ---- tests/com/jsyn/util/TestFFT.java | 207 ------ tests/com/jsyn/util/TestPseudoRandom.java | 82 --- tests/com/jsyn/util/TestVoiceAllocator.java | 111 ---- 675 files changed, 36335 insertions(+), 35883 deletions(-) delete mode 100644 .classpath delete mode 100644 .project create mode 100644 DEVELOPING.md delete mode 100644 README.txt create mode 100644 android/README.md create mode 100644 build.gradle delete mode 100644 build.xml create mode 100644 examples/build.gradle create mode 100644 examples/src/main/java/com/jsyn/examples/AudioPassThrough.java create mode 100644 examples/src/main/java/com/jsyn/examples/ChebyshevSong.java create mode 100644 examples/src/main/java/com/jsyn/examples/CircuitTester.java create mode 100644 examples/src/main/java/com/jsyn/examples/CustomCubeUnit.java create mode 100644 examples/src/main/java/com/jsyn/examples/DualOscilloscope.java create mode 100644 examples/src/main/java/com/jsyn/examples/EditEnvelope1.java create mode 100644 examples/src/main/java/com/jsyn/examples/FFTPassthrough.java create mode 100644 examples/src/main/java/com/jsyn/examples/GoogleWaveOscillator.java create mode 100644 examples/src/main/java/com/jsyn/examples/HearDAHDSR.java create mode 100644 examples/src/main/java/com/jsyn/examples/HearMoogFilter.java create mode 100644 examples/src/main/java/com/jsyn/examples/HearSinePM.java create mode 100644 examples/src/main/java/com/jsyn/examples/HearSpectralFilter.java create mode 100644 examples/src/main/java/com/jsyn/examples/ListAudioDevices.java create mode 100644 examples/src/main/java/com/jsyn/examples/LongEcho.java create mode 100644 examples/src/main/java/com/jsyn/examples/MonoPassThrough.java create mode 100644 examples/src/main/java/com/jsyn/examples/NotesToTone.java create mode 100644 examples/src/main/java/com/jsyn/examples/PlayChords.java create mode 100644 examples/src/main/java/com/jsyn/examples/PlayCustomUnit.java create mode 100644 examples/src/main/java/com/jsyn/examples/PlayFunction.java create mode 100644 examples/src/main/java/com/jsyn/examples/PlayGrains.java create mode 100644 examples/src/main/java/com/jsyn/examples/PlayMIDI.java create mode 100644 examples/src/main/java/com/jsyn/examples/PlayNotes.java create mode 100644 examples/src/main/java/com/jsyn/examples/PlayPartials.java create mode 100644 examples/src/main/java/com/jsyn/examples/PlaySample.java create mode 100644 examples/src/main/java/com/jsyn/examples/PlaySampleCrossfade.java create mode 100644 examples/src/main/java/com/jsyn/examples/PlaySampleWaveShaper.java create mode 100644 examples/src/main/java/com/jsyn/examples/PlaySegmentedEnvelope.java create mode 100644 examples/src/main/java/com/jsyn/examples/PlaySegmentedEnvelopeCallback.java create mode 100644 examples/src/main/java/com/jsyn/examples/PlaySequence.java create mode 100644 examples/src/main/java/com/jsyn/examples/PlayTone.java create mode 100644 examples/src/main/java/com/jsyn/examples/RecordSineSweep.java create mode 100644 examples/src/main/java/com/jsyn/examples/SampleHoldNoteBlaster.java create mode 100644 examples/src/main/java/com/jsyn/examples/SawFaders.java create mode 100644 examples/src/main/java/com/jsyn/examples/SeeGoogleWave.java create mode 100644 examples/src/main/java/com/jsyn/examples/SeeOscillators.java create mode 100644 examples/src/main/java/com/jsyn/examples/ShowWaves.java create mode 100644 examples/src/main/java/com/jsyn/examples/SwarmOfOscillators.java create mode 100644 examples/src/main/java/com/jsyn/examples/UseMidiKeyboard.java create mode 100644 examples/src/main/java/com/jsyn/examples/WindCircuit.java create mode 100644 examples/src/main/resources/log4j.xml create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100644 gradlew create mode 100644 gradlew.bat create mode 100644 settings.gradle delete mode 100644 src/com/jsyn/JSyn.java delete mode 100644 src/com/jsyn/Synthesizer.java delete mode 100644 src/com/jsyn/apps/AboutJSyn.java delete mode 100644 src/com/jsyn/apps/InstrumentTester.java delete mode 100644 src/com/jsyn/data/AudioSample.java delete mode 100644 src/com/jsyn/data/DoubleTable.java delete mode 100644 src/com/jsyn/data/FloatSample.java delete mode 100644 src/com/jsyn/data/Function.java delete mode 100644 src/com/jsyn/data/HammingWindow.java delete mode 100644 src/com/jsyn/data/HannWindow.java delete mode 100644 src/com/jsyn/data/SampleMarker.java delete mode 100644 src/com/jsyn/data/SegmentedEnvelope.java delete mode 100644 src/com/jsyn/data/SequentialData.java delete mode 100644 src/com/jsyn/data/SequentialDataCommon.java delete mode 100644 src/com/jsyn/data/ShortSample.java delete mode 100644 src/com/jsyn/data/SpectralWindow.java delete mode 100644 src/com/jsyn/data/SpectralWindowFactory.java delete mode 100644 src/com/jsyn/data/Spectrum.java delete mode 100644 src/com/jsyn/devices/AudioDeviceFactory.java delete mode 100644 src/com/jsyn/devices/AudioDeviceInputStream.java delete mode 100644 src/com/jsyn/devices/AudioDeviceManager.java delete mode 100644 src/com/jsyn/devices/AudioDeviceOutputStream.java delete mode 100644 src/com/jsyn/devices/javasound/JavaSoundAudioDevice.java delete mode 100644 src/com/jsyn/devices/javasound/MidiDeviceTools.java delete mode 100644 src/com/jsyn/devices/jportaudio/JPortAudioDevice.java delete mode 100644 src/com/jsyn/engine/LoadAnalyzer.java delete mode 100644 src/com/jsyn/engine/MultiTable.java delete mode 100644 src/com/jsyn/engine/SynthesisEngine.java delete mode 100644 src/com/jsyn/exceptions/ChannelMismatchException.java delete mode 100644 src/com/jsyn/instruments/DrumWoodFM.java delete mode 100644 src/com/jsyn/instruments/DualOscillatorSynthVoice.java delete mode 100644 src/com/jsyn/instruments/JSynInstrumentLibrary.java delete mode 100644 src/com/jsyn/instruments/NoiseHit.java delete mode 100644 src/com/jsyn/instruments/SubtractiveSynthVoice.java delete mode 100644 src/com/jsyn/instruments/WaveShapingVoice.java delete mode 100644 src/com/jsyn/io/AudioFifo.java delete mode 100644 src/com/jsyn/io/AudioInputStream.java delete mode 100644 src/com/jsyn/io/AudioOutputStream.java delete mode 100644 src/com/jsyn/midi/MessageParser.java delete mode 100644 src/com/jsyn/midi/MidiConstants.java delete mode 100644 src/com/jsyn/midi/MidiSynthesizer.java delete mode 100644 src/com/jsyn/package.html delete mode 100644 src/com/jsyn/ports/ConnectableInput.java delete mode 100644 src/com/jsyn/ports/ConnectableOutput.java delete mode 100644 src/com/jsyn/ports/GettablePort.java delete mode 100644 src/com/jsyn/ports/InputMixingBlockPart.java delete mode 100644 src/com/jsyn/ports/PortBlockPart.java delete mode 100644 src/com/jsyn/ports/QueueDataCommand.java delete mode 100644 src/com/jsyn/ports/QueueDataEvent.java delete mode 100644 src/com/jsyn/ports/SequentialDataCrossfade.java delete mode 100644 src/com/jsyn/ports/SettablePort.java delete mode 100644 src/com/jsyn/ports/UnitBlockPort.java delete mode 100644 src/com/jsyn/ports/UnitDataQueueCallback.java delete mode 100644 src/com/jsyn/ports/UnitDataQueuePort.java delete mode 100644 src/com/jsyn/ports/UnitFunctionPort.java delete mode 100644 src/com/jsyn/ports/UnitGatePort.java delete mode 100644 src/com/jsyn/ports/UnitInputPort.java delete mode 100644 src/com/jsyn/ports/UnitOutputPort.java delete mode 100644 src/com/jsyn/ports/UnitPort.java delete mode 100644 src/com/jsyn/ports/UnitSpectralInputPort.java delete mode 100644 src/com/jsyn/ports/UnitSpectralOutputPort.java delete mode 100644 src/com/jsyn/ports/UnitVariablePort.java delete mode 100644 src/com/jsyn/ports/package.html delete mode 100644 src/com/jsyn/scope/AudioScope.java delete mode 100644 src/com/jsyn/scope/AudioScopeModel.java delete mode 100644 src/com/jsyn/scope/AudioScopeProbe.java delete mode 100644 src/com/jsyn/scope/DefaultWaveTraceModel.java delete mode 100644 src/com/jsyn/scope/MultiChannelScopeProbeUnit.java delete mode 100644 src/com/jsyn/scope/TriggerModel.java delete mode 100644 src/com/jsyn/scope/WaveTraceModel.java delete mode 100644 src/com/jsyn/scope/swing/AudioScopeProbeView.java delete mode 100644 src/com/jsyn/scope/swing/AudioScopeView.java delete mode 100644 src/com/jsyn/scope/swing/MultipleWaveDisplay.java delete mode 100644 src/com/jsyn/scope/swing/ScopeControlPanel.java delete mode 100644 src/com/jsyn/scope/swing/ScopeProbePanel.java delete mode 100644 src/com/jsyn/scope/swing/ScopeTriggerPanel.java delete mode 100644 src/com/jsyn/scope/swing/WaveTraceView.java delete mode 100644 src/com/jsyn/swing/ASCIIMusicKeyboard.java delete mode 100644 src/com/jsyn/swing/DoubleBoundedRangeModel.java delete mode 100644 src/com/jsyn/swing/DoubleBoundedRangeSlider.java delete mode 100644 src/com/jsyn/swing/DoubleBoundedTextField.java delete mode 100644 src/com/jsyn/swing/EnvelopeEditorBox.java delete mode 100644 src/com/jsyn/swing/EnvelopeEditorPanel.java delete mode 100644 src/com/jsyn/swing/EnvelopePoints.java delete mode 100644 src/com/jsyn/swing/ExponentialRangeModel.java delete mode 100644 src/com/jsyn/swing/InstrumentBrowser.java delete mode 100644 src/com/jsyn/swing/JAppletFrame.java delete mode 100644 src/com/jsyn/swing/PortBoundedRangeModel.java delete mode 100644 src/com/jsyn/swing/PortControllerFactory.java delete mode 100644 src/com/jsyn/swing/PortModelFactory.java delete mode 100644 src/com/jsyn/swing/PresetSelectionListener.java delete mode 100644 src/com/jsyn/swing/RotaryController.java delete mode 100644 src/com/jsyn/swing/RotaryTextController.java delete mode 100644 src/com/jsyn/swing/SoundTweaker.java delete mode 100644 src/com/jsyn/swing/XYController.java delete mode 100644 src/com/jsyn/unitgen/Add.java delete mode 100644 src/com/jsyn/unitgen/AsymptoticRamp.java delete mode 100644 src/com/jsyn/unitgen/BrownNoise.java delete mode 100644 src/com/jsyn/unitgen/ChannelIn.java delete mode 100644 src/com/jsyn/unitgen/ChannelOut.java delete mode 100644 src/com/jsyn/unitgen/Circuit.java delete mode 100644 src/com/jsyn/unitgen/Compare.java delete mode 100644 src/com/jsyn/unitgen/ContinuousRamp.java delete mode 100644 src/com/jsyn/unitgen/CrossFade.java delete mode 100644 src/com/jsyn/unitgen/Delay.java delete mode 100644 src/com/jsyn/unitgen/Divide.java delete mode 100644 src/com/jsyn/unitgen/DualInTwoOut.java delete mode 100644 src/com/jsyn/unitgen/EdgeDetector.java delete mode 100644 src/com/jsyn/unitgen/EnvelopeAttackDecay.java delete mode 100644 src/com/jsyn/unitgen/EnvelopeDAHDSR.java delete mode 100644 src/com/jsyn/unitgen/ExponentialRamp.java delete mode 100644 src/com/jsyn/unitgen/FFT.java delete mode 100644 src/com/jsyn/unitgen/FFTBase.java delete mode 100644 src/com/jsyn/unitgen/FilterAllPass.java delete mode 100644 src/com/jsyn/unitgen/FilterBandPass.java delete mode 100644 src/com/jsyn/unitgen/FilterBandStop.java delete mode 100644 src/com/jsyn/unitgen/FilterBiquad.java delete mode 100644 src/com/jsyn/unitgen/FilterBiquadCommon.java delete mode 100644 src/com/jsyn/unitgen/FilterBiquadShelf.java delete mode 100644 src/com/jsyn/unitgen/FilterFourPoles.java delete mode 100644 src/com/jsyn/unitgen/FilterHighPass.java delete mode 100644 src/com/jsyn/unitgen/FilterHighShelf.java delete mode 100644 src/com/jsyn/unitgen/FilterLowPass.java delete mode 100644 src/com/jsyn/unitgen/FilterLowShelf.java delete mode 100644 src/com/jsyn/unitgen/FilterOnePole.java delete mode 100644 src/com/jsyn/unitgen/FilterOnePoleOneZero.java delete mode 100644 src/com/jsyn/unitgen/FilterOneZero.java delete mode 100644 src/com/jsyn/unitgen/FilterPeakingEQ.java delete mode 100644 src/com/jsyn/unitgen/FilterStateVariable.java delete mode 100644 src/com/jsyn/unitgen/FilterTwoPoles.java delete mode 100644 src/com/jsyn/unitgen/FilterTwoPolesTwoZeros.java delete mode 100644 src/com/jsyn/unitgen/FixedRateMonoReader.java delete mode 100644 src/com/jsyn/unitgen/FixedRateMonoWriter.java delete mode 100644 src/com/jsyn/unitgen/FixedRateStereoReader.java delete mode 100644 src/com/jsyn/unitgen/FixedRateStereoWriter.java delete mode 100644 src/com/jsyn/unitgen/FourWayFade.java delete mode 100644 src/com/jsyn/unitgen/FunctionEvaluator.java delete mode 100644 src/com/jsyn/unitgen/FunctionOscillator.java delete mode 100644 src/com/jsyn/unitgen/Grain.java delete mode 100644 src/com/jsyn/unitgen/GrainCommon.java delete mode 100644 src/com/jsyn/unitgen/GrainEnvelope.java delete mode 100644 src/com/jsyn/unitgen/GrainFarm.java delete mode 100644 src/com/jsyn/unitgen/GrainScheduler.java delete mode 100644 src/com/jsyn/unitgen/GrainSource.java delete mode 100644 src/com/jsyn/unitgen/GrainSourceSine.java delete mode 100644 src/com/jsyn/unitgen/IFFT.java delete mode 100644 src/com/jsyn/unitgen/ImpulseOscillator.java delete mode 100644 src/com/jsyn/unitgen/ImpulseOscillatorBL.java delete mode 100644 src/com/jsyn/unitgen/Integrate.java delete mode 100644 src/com/jsyn/unitgen/InterpolatingDelay.java delete mode 100644 src/com/jsyn/unitgen/Latch.java delete mode 100644 src/com/jsyn/unitgen/LatchZeroCrossing.java delete mode 100644 src/com/jsyn/unitgen/LineIn.java delete mode 100644 src/com/jsyn/unitgen/LineOut.java delete mode 100644 src/com/jsyn/unitgen/LinearRamp.java delete mode 100644 src/com/jsyn/unitgen/Maximum.java delete mode 100644 src/com/jsyn/unitgen/Minimum.java delete mode 100644 src/com/jsyn/unitgen/MixerMono.java delete mode 100644 src/com/jsyn/unitgen/MixerMonoRamped.java delete mode 100644 src/com/jsyn/unitgen/MixerStereo.java delete mode 100644 src/com/jsyn/unitgen/MixerStereoRamped.java delete mode 100644 src/com/jsyn/unitgen/MonoStreamWriter.java delete mode 100644 src/com/jsyn/unitgen/MorphingOscillatorBL.java delete mode 100644 src/com/jsyn/unitgen/MultiPassThrough.java delete mode 100644 src/com/jsyn/unitgen/Multiply.java delete mode 100644 src/com/jsyn/unitgen/MultiplyAdd.java delete mode 100644 src/com/jsyn/unitgen/Pan.java delete mode 100644 src/com/jsyn/unitgen/PanControl.java delete mode 100644 src/com/jsyn/unitgen/ParabolicEnvelope.java delete mode 100644 src/com/jsyn/unitgen/PassThrough.java delete mode 100644 src/com/jsyn/unitgen/PeakFollower.java delete mode 100644 src/com/jsyn/unitgen/PhaseShifter.java delete mode 100644 src/com/jsyn/unitgen/PinkNoise.java delete mode 100644 src/com/jsyn/unitgen/PitchDetector.java delete mode 100644 src/com/jsyn/unitgen/PitchToFrequency.java delete mode 100644 src/com/jsyn/unitgen/PowerOfTwo.java delete mode 100644 src/com/jsyn/unitgen/PulseOscillator.java delete mode 100644 src/com/jsyn/unitgen/PulseOscillatorBL.java delete mode 100644 src/com/jsyn/unitgen/RaisedCosineEnvelope.java delete mode 100644 src/com/jsyn/unitgen/RangeConverter.java delete mode 100644 src/com/jsyn/unitgen/RectangularWindow.java delete mode 100644 src/com/jsyn/unitgen/RedNoise.java delete mode 100644 src/com/jsyn/unitgen/SampleGrainFarm.java delete mode 100644 src/com/jsyn/unitgen/SampleGrainSource.java delete mode 100644 src/com/jsyn/unitgen/SawtoothOscillator.java delete mode 100644 src/com/jsyn/unitgen/SawtoothOscillatorBL.java delete mode 100644 src/com/jsyn/unitgen/SawtoothOscillatorDPW.java delete mode 100644 src/com/jsyn/unitgen/SchmidtTrigger.java delete mode 100644 src/com/jsyn/unitgen/Select.java delete mode 100644 src/com/jsyn/unitgen/SequentialDataReader.java delete mode 100644 src/com/jsyn/unitgen/SequentialDataWriter.java delete mode 100644 src/com/jsyn/unitgen/SineOscillator.java delete mode 100644 src/com/jsyn/unitgen/SineOscillatorPhaseModulated.java delete mode 100644 src/com/jsyn/unitgen/SpectralFFT.java delete mode 100644 src/com/jsyn/unitgen/SpectralFilter.java delete mode 100644 src/com/jsyn/unitgen/SpectralIFFT.java delete mode 100644 src/com/jsyn/unitgen/SpectralProcessor.java delete mode 100644 src/com/jsyn/unitgen/SquareOscillator.java delete mode 100644 src/com/jsyn/unitgen/SquareOscillatorBL.java delete mode 100644 src/com/jsyn/unitgen/StereoStreamWriter.java delete mode 100644 src/com/jsyn/unitgen/StochasticGrainScheduler.java delete mode 100644 src/com/jsyn/unitgen/Subtract.java delete mode 100644 src/com/jsyn/unitgen/TriangleOscillator.java delete mode 100644 src/com/jsyn/unitgen/TunableFilter.java delete mode 100644 src/com/jsyn/unitgen/TwoInDualOut.java delete mode 100644 src/com/jsyn/unitgen/UnitBinaryOperator.java delete mode 100644 src/com/jsyn/unitgen/UnitFilter.java delete mode 100644 src/com/jsyn/unitgen/UnitGate.java delete mode 100644 src/com/jsyn/unitgen/UnitGenerator.java delete mode 100644 src/com/jsyn/unitgen/UnitOscillator.java delete mode 100644 src/com/jsyn/unitgen/UnitSink.java delete mode 100644 src/com/jsyn/unitgen/UnitSource.java delete mode 100644 src/com/jsyn/unitgen/UnitStreamWriter.java delete mode 100644 src/com/jsyn/unitgen/UnitVoice.java delete mode 100644 src/com/jsyn/unitgen/Unzipper.java delete mode 100644 src/com/jsyn/unitgen/VariableRateDataReader.java delete mode 100644 src/com/jsyn/unitgen/VariableRateMonoReader.java delete mode 100644 src/com/jsyn/unitgen/VariableRateStereoReader.java delete mode 100644 src/com/jsyn/unitgen/WhiteNoise.java delete mode 100644 src/com/jsyn/unitgen/ZeroCrossingCounter.java delete mode 100644 src/com/jsyn/util/AudioSampleLoader.java delete mode 100644 src/com/jsyn/util/AudioStreamReader.java delete mode 100644 src/com/jsyn/util/AutoCorrelator.java delete mode 100644 src/com/jsyn/util/Instrument.java delete mode 100644 src/com/jsyn/util/InstrumentLibrary.java delete mode 100644 src/com/jsyn/util/JavaSoundSampleLoader.java delete mode 100644 src/com/jsyn/util/JavaTools.java delete mode 100644 src/com/jsyn/util/MultiChannelSynthesizer.java delete mode 100644 src/com/jsyn/util/NumericOutput.java delete mode 100644 src/com/jsyn/util/PolyphonicInstrument.java delete mode 100644 src/com/jsyn/util/PseudoRandom.java delete mode 100644 src/com/jsyn/util/RecursiveSequenceGenerator.java delete mode 100644 src/com/jsyn/util/SampleLoader.java delete mode 100644 src/com/jsyn/util/SignalCorrelator.java delete mode 100644 src/com/jsyn/util/StreamingThread.java delete mode 100644 src/com/jsyn/util/TransportListener.java delete mode 100644 src/com/jsyn/util/TransportModel.java delete mode 100644 src/com/jsyn/util/VoiceAllocator.java delete mode 100644 src/com/jsyn/util/VoiceDescription.java delete mode 100644 src/com/jsyn/util/VoiceOperation.java delete mode 100644 src/com/jsyn/util/WaveFileWriter.java delete mode 100644 src/com/jsyn/util/WaveRecorder.java delete mode 100644 src/com/jsyn/util/soundfile/AIFFFileParser.java delete mode 100644 src/com/jsyn/util/soundfile/AudioFileParser.java delete mode 100644 src/com/jsyn/util/soundfile/ChunkHandler.java delete mode 100644 src/com/jsyn/util/soundfile/CustomSampleLoader.java delete mode 100644 src/com/jsyn/util/soundfile/IFFParser.java delete mode 100644 src/com/jsyn/util/soundfile/WAVEFileParser.java delete mode 100644 src/com/softsynth/math/AudioMath.java delete mode 100644 src/com/softsynth/math/ChebyshevPolynomial.java delete mode 100644 src/com/softsynth/math/FourierMath.java delete mode 100644 src/com/softsynth/math/JustRatio.java delete mode 100644 src/com/softsynth/math/Polynomial.java delete mode 100644 src/com/softsynth/math/PolynomialTableData.java delete mode 100644 src/com/softsynth/math/PrimeFactors.java delete mode 100644 src/com/softsynth/shared/time/ScheduledCommand.java delete mode 100644 src/com/softsynth/shared/time/ScheduledQueue.java delete mode 100644 src/com/softsynth/shared/time/TimeStamp.java create mode 100644 src/main/java/com/jsyn/JSyn.java create mode 100644 src/main/java/com/jsyn/Synthesizer.java create mode 100644 src/main/java/com/jsyn/apps/AboutJSyn.java create mode 100644 src/main/java/com/jsyn/apps/InstrumentTester.java create mode 100644 src/main/java/com/jsyn/data/AudioSample.java create mode 100644 src/main/java/com/jsyn/data/DoubleTable.java create mode 100644 src/main/java/com/jsyn/data/FloatSample.java create mode 100644 src/main/java/com/jsyn/data/Function.java create mode 100644 src/main/java/com/jsyn/data/HammingWindow.java create mode 100644 src/main/java/com/jsyn/data/HannWindow.java create mode 100644 src/main/java/com/jsyn/data/SampleMarker.java create mode 100644 src/main/java/com/jsyn/data/SegmentedEnvelope.java create mode 100644 src/main/java/com/jsyn/data/SequentialData.java create mode 100644 src/main/java/com/jsyn/data/SequentialDataCommon.java create mode 100644 src/main/java/com/jsyn/data/ShortSample.java create mode 100644 src/main/java/com/jsyn/data/SpectralWindow.java create mode 100644 src/main/java/com/jsyn/data/SpectralWindowFactory.java create mode 100644 src/main/java/com/jsyn/data/Spectrum.java create mode 100644 src/main/java/com/jsyn/devices/AudioDeviceFactory.java create mode 100644 src/main/java/com/jsyn/devices/AudioDeviceInputStream.java create mode 100644 src/main/java/com/jsyn/devices/AudioDeviceManager.java create mode 100644 src/main/java/com/jsyn/devices/AudioDeviceOutputStream.java create mode 100644 src/main/java/com/jsyn/devices/javasound/JavaSoundAudioDevice.java create mode 100644 src/main/java/com/jsyn/devices/javasound/MidiDeviceTools.java create mode 100644 src/main/java/com/jsyn/devices/jportaudio/JPortAudioDevice.java create mode 100644 src/main/java/com/jsyn/engine/LoadAnalyzer.java create mode 100644 src/main/java/com/jsyn/engine/MultiTable.java create mode 100644 src/main/java/com/jsyn/engine/SynthesisEngine.java create mode 100644 src/main/java/com/jsyn/exceptions/ChannelMismatchException.java create mode 100644 src/main/java/com/jsyn/instruments/DrumWoodFM.java create mode 100644 src/main/java/com/jsyn/instruments/DualOscillatorSynthVoice.java create mode 100644 src/main/java/com/jsyn/instruments/JSynInstrumentLibrary.java create mode 100644 src/main/java/com/jsyn/instruments/NoiseHit.java create mode 100644 src/main/java/com/jsyn/instruments/SubtractiveSynthVoice.java create mode 100644 src/main/java/com/jsyn/instruments/WaveShapingVoice.java create mode 100644 src/main/java/com/jsyn/io/AudioFifo.java create mode 100644 src/main/java/com/jsyn/io/AudioInputStream.java create mode 100644 src/main/java/com/jsyn/io/AudioOutputStream.java create mode 100644 src/main/java/com/jsyn/midi/MessageParser.java create mode 100644 src/main/java/com/jsyn/midi/MidiConstants.java create mode 100644 src/main/java/com/jsyn/midi/MidiSynthesizer.java create mode 100644 src/main/java/com/jsyn/package.html create mode 100644 src/main/java/com/jsyn/ports/ConnectableInput.java create mode 100644 src/main/java/com/jsyn/ports/ConnectableOutput.java create mode 100644 src/main/java/com/jsyn/ports/GettablePort.java create mode 100644 src/main/java/com/jsyn/ports/InputMixingBlockPart.java create mode 100644 src/main/java/com/jsyn/ports/PortBlockPart.java create mode 100644 src/main/java/com/jsyn/ports/QueueDataCommand.java create mode 100644 src/main/java/com/jsyn/ports/QueueDataEvent.java create mode 100644 src/main/java/com/jsyn/ports/SequentialDataCrossfade.java create mode 100644 src/main/java/com/jsyn/ports/SettablePort.java create mode 100644 src/main/java/com/jsyn/ports/UnitBlockPort.java create mode 100644 src/main/java/com/jsyn/ports/UnitDataQueueCallback.java create mode 100644 src/main/java/com/jsyn/ports/UnitDataQueuePort.java create mode 100644 src/main/java/com/jsyn/ports/UnitFunctionPort.java create mode 100644 src/main/java/com/jsyn/ports/UnitGatePort.java create mode 100644 src/main/java/com/jsyn/ports/UnitInputPort.java create mode 100644 src/main/java/com/jsyn/ports/UnitOutputPort.java create mode 100644 src/main/java/com/jsyn/ports/UnitPort.java create mode 100644 src/main/java/com/jsyn/ports/UnitSpectralInputPort.java create mode 100644 src/main/java/com/jsyn/ports/UnitSpectralOutputPort.java create mode 100644 src/main/java/com/jsyn/ports/UnitVariablePort.java create mode 100644 src/main/java/com/jsyn/ports/package.html create mode 100644 src/main/java/com/jsyn/scope/AudioScope.java create mode 100644 src/main/java/com/jsyn/scope/AudioScopeModel.java create mode 100644 src/main/java/com/jsyn/scope/AudioScopeProbe.java create mode 100644 src/main/java/com/jsyn/scope/DefaultWaveTraceModel.java create mode 100644 src/main/java/com/jsyn/scope/MultiChannelScopeProbeUnit.java create mode 100644 src/main/java/com/jsyn/scope/TriggerModel.java create mode 100644 src/main/java/com/jsyn/scope/WaveTraceModel.java create mode 100644 src/main/java/com/jsyn/scope/swing/AudioScopeProbeView.java create mode 100644 src/main/java/com/jsyn/scope/swing/AudioScopeView.java create mode 100644 src/main/java/com/jsyn/scope/swing/MultipleWaveDisplay.java create mode 100644 src/main/java/com/jsyn/scope/swing/ScopeControlPanel.java create mode 100644 src/main/java/com/jsyn/scope/swing/ScopeProbePanel.java create mode 100644 src/main/java/com/jsyn/scope/swing/ScopeTriggerPanel.java create mode 100644 src/main/java/com/jsyn/scope/swing/WaveTraceView.java create mode 100644 src/main/java/com/jsyn/swing/ASCIIMusicKeyboard.java create mode 100644 src/main/java/com/jsyn/swing/DoubleBoundedRangeModel.java create mode 100644 src/main/java/com/jsyn/swing/DoubleBoundedRangeSlider.java create mode 100644 src/main/java/com/jsyn/swing/DoubleBoundedTextField.java create mode 100644 src/main/java/com/jsyn/swing/EnvelopeEditorBox.java create mode 100644 src/main/java/com/jsyn/swing/EnvelopeEditorPanel.java create mode 100644 src/main/java/com/jsyn/swing/EnvelopePoints.java create mode 100644 src/main/java/com/jsyn/swing/ExponentialRangeModel.java create mode 100644 src/main/java/com/jsyn/swing/InstrumentBrowser.java create mode 100644 src/main/java/com/jsyn/swing/JAppletFrame.java create mode 100644 src/main/java/com/jsyn/swing/PortBoundedRangeModel.java create mode 100644 src/main/java/com/jsyn/swing/PortControllerFactory.java create mode 100644 src/main/java/com/jsyn/swing/PortModelFactory.java create mode 100644 src/main/java/com/jsyn/swing/PresetSelectionListener.java create mode 100644 src/main/java/com/jsyn/swing/RotaryController.java create mode 100644 src/main/java/com/jsyn/swing/RotaryTextController.java create mode 100644 src/main/java/com/jsyn/swing/SoundTweaker.java create mode 100644 src/main/java/com/jsyn/swing/XYController.java create mode 100644 src/main/java/com/jsyn/unitgen/Add.java create mode 100644 src/main/java/com/jsyn/unitgen/AsymptoticRamp.java create mode 100644 src/main/java/com/jsyn/unitgen/BrownNoise.java create mode 100644 src/main/java/com/jsyn/unitgen/ChannelIn.java create mode 100644 src/main/java/com/jsyn/unitgen/ChannelOut.java create mode 100644 src/main/java/com/jsyn/unitgen/Circuit.java create mode 100644 src/main/java/com/jsyn/unitgen/Compare.java create mode 100644 src/main/java/com/jsyn/unitgen/ContinuousRamp.java create mode 100644 src/main/java/com/jsyn/unitgen/CrossFade.java create mode 100644 src/main/java/com/jsyn/unitgen/Delay.java create mode 100644 src/main/java/com/jsyn/unitgen/Divide.java create mode 100644 src/main/java/com/jsyn/unitgen/DualInTwoOut.java create mode 100644 src/main/java/com/jsyn/unitgen/EdgeDetector.java create mode 100644 src/main/java/com/jsyn/unitgen/EnvelopeAttackDecay.java create mode 100644 src/main/java/com/jsyn/unitgen/EnvelopeDAHDSR.java create mode 100644 src/main/java/com/jsyn/unitgen/ExponentialRamp.java create mode 100644 src/main/java/com/jsyn/unitgen/FFT.java create mode 100644 src/main/java/com/jsyn/unitgen/FFTBase.java create mode 100644 src/main/java/com/jsyn/unitgen/FilterAllPass.java create mode 100644 src/main/java/com/jsyn/unitgen/FilterBandPass.java create mode 100644 src/main/java/com/jsyn/unitgen/FilterBandStop.java create mode 100644 src/main/java/com/jsyn/unitgen/FilterBiquad.java create mode 100644 src/main/java/com/jsyn/unitgen/FilterBiquadCommon.java create mode 100644 src/main/java/com/jsyn/unitgen/FilterBiquadShelf.java create mode 100644 src/main/java/com/jsyn/unitgen/FilterFourPoles.java create mode 100644 src/main/java/com/jsyn/unitgen/FilterHighPass.java create mode 100644 src/main/java/com/jsyn/unitgen/FilterHighShelf.java create mode 100644 src/main/java/com/jsyn/unitgen/FilterLowPass.java create mode 100644 src/main/java/com/jsyn/unitgen/FilterLowShelf.java create mode 100644 src/main/java/com/jsyn/unitgen/FilterOnePole.java create mode 100644 src/main/java/com/jsyn/unitgen/FilterOnePoleOneZero.java create mode 100644 src/main/java/com/jsyn/unitgen/FilterOneZero.java create mode 100644 src/main/java/com/jsyn/unitgen/FilterPeakingEQ.java create mode 100644 src/main/java/com/jsyn/unitgen/FilterStateVariable.java create mode 100644 src/main/java/com/jsyn/unitgen/FilterTwoPoles.java create mode 100644 src/main/java/com/jsyn/unitgen/FilterTwoPolesTwoZeros.java create mode 100644 src/main/java/com/jsyn/unitgen/FixedRateMonoReader.java create mode 100644 src/main/java/com/jsyn/unitgen/FixedRateMonoWriter.java create mode 100644 src/main/java/com/jsyn/unitgen/FixedRateStereoReader.java create mode 100644 src/main/java/com/jsyn/unitgen/FixedRateStereoWriter.java create mode 100644 src/main/java/com/jsyn/unitgen/FourWayFade.java create mode 100644 src/main/java/com/jsyn/unitgen/FunctionEvaluator.java create mode 100644 src/main/java/com/jsyn/unitgen/FunctionOscillator.java create mode 100644 src/main/java/com/jsyn/unitgen/Grain.java create mode 100644 src/main/java/com/jsyn/unitgen/GrainCommon.java create mode 100644 src/main/java/com/jsyn/unitgen/GrainEnvelope.java create mode 100644 src/main/java/com/jsyn/unitgen/GrainFarm.java create mode 100644 src/main/java/com/jsyn/unitgen/GrainScheduler.java create mode 100644 src/main/java/com/jsyn/unitgen/GrainSource.java create mode 100644 src/main/java/com/jsyn/unitgen/GrainSourceSine.java create mode 100644 src/main/java/com/jsyn/unitgen/IFFT.java create mode 100644 src/main/java/com/jsyn/unitgen/ImpulseOscillator.java create mode 100644 src/main/java/com/jsyn/unitgen/ImpulseOscillatorBL.java create mode 100644 src/main/java/com/jsyn/unitgen/Integrate.java create mode 100644 src/main/java/com/jsyn/unitgen/InterpolatingDelay.java create mode 100644 src/main/java/com/jsyn/unitgen/Latch.java create mode 100644 src/main/java/com/jsyn/unitgen/LatchZeroCrossing.java create mode 100644 src/main/java/com/jsyn/unitgen/LineIn.java create mode 100644 src/main/java/com/jsyn/unitgen/LineOut.java create mode 100644 src/main/java/com/jsyn/unitgen/LinearRamp.java create mode 100644 src/main/java/com/jsyn/unitgen/Maximum.java create mode 100644 src/main/java/com/jsyn/unitgen/Minimum.java create mode 100644 src/main/java/com/jsyn/unitgen/MixerMono.java create mode 100644 src/main/java/com/jsyn/unitgen/MixerMonoRamped.java create mode 100644 src/main/java/com/jsyn/unitgen/MixerStereo.java create mode 100644 src/main/java/com/jsyn/unitgen/MixerStereoRamped.java create mode 100644 src/main/java/com/jsyn/unitgen/MonoStreamWriter.java create mode 100644 src/main/java/com/jsyn/unitgen/MorphingOscillatorBL.java create mode 100644 src/main/java/com/jsyn/unitgen/MultiPassThrough.java create mode 100644 src/main/java/com/jsyn/unitgen/Multiply.java create mode 100644 src/main/java/com/jsyn/unitgen/MultiplyAdd.java create mode 100644 src/main/java/com/jsyn/unitgen/Pan.java create mode 100644 src/main/java/com/jsyn/unitgen/PanControl.java create mode 100644 src/main/java/com/jsyn/unitgen/ParabolicEnvelope.java create mode 100644 src/main/java/com/jsyn/unitgen/PassThrough.java create mode 100644 src/main/java/com/jsyn/unitgen/PeakFollower.java create mode 100644 src/main/java/com/jsyn/unitgen/PhaseShifter.java create mode 100644 src/main/java/com/jsyn/unitgen/PinkNoise.java create mode 100644 src/main/java/com/jsyn/unitgen/PitchDetector.java create mode 100644 src/main/java/com/jsyn/unitgen/PitchToFrequency.java create mode 100644 src/main/java/com/jsyn/unitgen/PowerOfTwo.java create mode 100644 src/main/java/com/jsyn/unitgen/PulseOscillator.java create mode 100644 src/main/java/com/jsyn/unitgen/PulseOscillatorBL.java create mode 100644 src/main/java/com/jsyn/unitgen/RaisedCosineEnvelope.java create mode 100644 src/main/java/com/jsyn/unitgen/RangeConverter.java create mode 100644 src/main/java/com/jsyn/unitgen/RectangularWindow.java create mode 100644 src/main/java/com/jsyn/unitgen/RedNoise.java create mode 100644 src/main/java/com/jsyn/unitgen/SampleGrainFarm.java create mode 100644 src/main/java/com/jsyn/unitgen/SampleGrainSource.java create mode 100644 src/main/java/com/jsyn/unitgen/SawtoothOscillator.java create mode 100644 src/main/java/com/jsyn/unitgen/SawtoothOscillatorBL.java create mode 100644 src/main/java/com/jsyn/unitgen/SawtoothOscillatorDPW.java create mode 100644 src/main/java/com/jsyn/unitgen/SchmidtTrigger.java create mode 100644 src/main/java/com/jsyn/unitgen/Select.java create mode 100644 src/main/java/com/jsyn/unitgen/SequentialDataReader.java create mode 100644 src/main/java/com/jsyn/unitgen/SequentialDataWriter.java create mode 100644 src/main/java/com/jsyn/unitgen/SineOscillator.java create mode 100644 src/main/java/com/jsyn/unitgen/SineOscillatorPhaseModulated.java create mode 100644 src/main/java/com/jsyn/unitgen/SpectralFFT.java create mode 100644 src/main/java/com/jsyn/unitgen/SpectralFilter.java create mode 100644 src/main/java/com/jsyn/unitgen/SpectralIFFT.java create mode 100644 src/main/java/com/jsyn/unitgen/SpectralProcessor.java create mode 100644 src/main/java/com/jsyn/unitgen/SquareOscillator.java create mode 100644 src/main/java/com/jsyn/unitgen/SquareOscillatorBL.java create mode 100644 src/main/java/com/jsyn/unitgen/StereoStreamWriter.java create mode 100644 src/main/java/com/jsyn/unitgen/StochasticGrainScheduler.java create mode 100644 src/main/java/com/jsyn/unitgen/Subtract.java create mode 100644 src/main/java/com/jsyn/unitgen/TriangleOscillator.java create mode 100644 src/main/java/com/jsyn/unitgen/TunableFilter.java create mode 100644 src/main/java/com/jsyn/unitgen/TwoInDualOut.java create mode 100644 src/main/java/com/jsyn/unitgen/UnitBinaryOperator.java create mode 100644 src/main/java/com/jsyn/unitgen/UnitFilter.java create mode 100644 src/main/java/com/jsyn/unitgen/UnitGate.java create mode 100644 src/main/java/com/jsyn/unitgen/UnitGenerator.java create mode 100644 src/main/java/com/jsyn/unitgen/UnitOscillator.java create mode 100644 src/main/java/com/jsyn/unitgen/UnitSink.java create mode 100644 src/main/java/com/jsyn/unitgen/UnitSource.java create mode 100644 src/main/java/com/jsyn/unitgen/UnitStreamWriter.java create mode 100644 src/main/java/com/jsyn/unitgen/UnitVoice.java create mode 100644 src/main/java/com/jsyn/unitgen/Unzipper.java create mode 100644 src/main/java/com/jsyn/unitgen/VariableRateDataReader.java create mode 100644 src/main/java/com/jsyn/unitgen/VariableRateMonoReader.java create mode 100644 src/main/java/com/jsyn/unitgen/VariableRateStereoReader.java create mode 100644 src/main/java/com/jsyn/unitgen/WhiteNoise.java create mode 100644 src/main/java/com/jsyn/unitgen/ZeroCrossingCounter.java create mode 100644 src/main/java/com/jsyn/util/AudioSampleLoader.java create mode 100644 src/main/java/com/jsyn/util/AudioStreamReader.java create mode 100644 src/main/java/com/jsyn/util/AutoCorrelator.java create mode 100644 src/main/java/com/jsyn/util/Instrument.java create mode 100644 src/main/java/com/jsyn/util/InstrumentLibrary.java create mode 100644 src/main/java/com/jsyn/util/JavaSoundSampleLoader.java create mode 100644 src/main/java/com/jsyn/util/JavaTools.java create mode 100644 src/main/java/com/jsyn/util/MultiChannelSynthesizer.java create mode 100644 src/main/java/com/jsyn/util/NumericOutput.java create mode 100644 src/main/java/com/jsyn/util/PolyphonicInstrument.java create mode 100644 src/main/java/com/jsyn/util/PseudoRandom.java create mode 100644 src/main/java/com/jsyn/util/RecursiveSequenceGenerator.java create mode 100644 src/main/java/com/jsyn/util/SampleLoader.java create mode 100644 src/main/java/com/jsyn/util/SignalCorrelator.java create mode 100644 src/main/java/com/jsyn/util/StreamingThread.java create mode 100644 src/main/java/com/jsyn/util/TransportListener.java create mode 100644 src/main/java/com/jsyn/util/TransportModel.java create mode 100644 src/main/java/com/jsyn/util/VoiceAllocator.java create mode 100644 src/main/java/com/jsyn/util/VoiceDescription.java create mode 100644 src/main/java/com/jsyn/util/VoiceOperation.java create mode 100644 src/main/java/com/jsyn/util/WaveFileWriter.java create mode 100644 src/main/java/com/jsyn/util/WaveRecorder.java create mode 100644 src/main/java/com/jsyn/util/soundfile/AIFFFileParser.java create mode 100644 src/main/java/com/jsyn/util/soundfile/AudioFileParser.java create mode 100644 src/main/java/com/jsyn/util/soundfile/ChunkHandler.java create mode 100644 src/main/java/com/jsyn/util/soundfile/CustomSampleLoader.java create mode 100644 src/main/java/com/jsyn/util/soundfile/IFFParser.java create mode 100644 src/main/java/com/jsyn/util/soundfile/WAVEFileParser.java create mode 100644 src/main/java/com/softsynth/math/AudioMath.java create mode 100644 src/main/java/com/softsynth/math/ChebyshevPolynomial.java create mode 100644 src/main/java/com/softsynth/math/FourierMath.java create mode 100644 src/main/java/com/softsynth/math/JustRatio.java create mode 100644 src/main/java/com/softsynth/math/Polynomial.java create mode 100644 src/main/java/com/softsynth/math/PolynomialTableData.java create mode 100644 src/main/java/com/softsynth/math/PrimeFactors.java create mode 100644 src/main/java/com/softsynth/shared/time/ScheduledCommand.java create mode 100644 src/main/java/com/softsynth/shared/time/ScheduledQueue.java create mode 100644 src/main/java/com/softsynth/shared/time/TimeStamp.java create mode 100644 src/main/resources/log4j.xml create mode 100644 src/test/java/com/jsyn/benchmarks/BenchJSyn.java create mode 100644 src/test/java/com/jsyn/data/TestShortSample.java create mode 100644 src/test/java/com/jsyn/engine/TestAudioOutput.java create mode 100644 src/test/java/com/jsyn/engine/TestDevices.java create mode 100644 src/test/java/com/jsyn/engine/TestEngine.java create mode 100644 src/test/java/com/jsyn/engine/TestFifo.java create mode 100644 src/test/java/com/jsyn/engine/TestWaveFileReadWrite.java create mode 100644 src/test/java/com/jsyn/midi/TestMidiLoop.java create mode 100644 src/test/java/com/jsyn/ports/TestQueuedDataPort.java create mode 100644 src/test/java/com/jsyn/ports/TestSequentialData.java create mode 100644 src/test/java/com/jsyn/ports/TestSet.java create mode 100644 src/test/java/com/jsyn/research/BenchMultiThreading.java create mode 100644 src/test/java/com/jsyn/research/RecordVariousRamps.java create mode 100644 src/test/java/com/jsyn/swing/TestRangeModels.java create mode 100644 src/test/java/com/jsyn/unitgen/CalibrateMoogFilter.java create mode 100644 src/test/java/com/jsyn/unitgen/EnablingGate.java create mode 100644 src/test/java/com/jsyn/unitgen/NonRealTimeTestCase.java create mode 100644 src/test/java/com/jsyn/unitgen/RecordMoogFilter.java create mode 100644 src/test/java/com/jsyn/unitgen/TestConnections.java create mode 100644 src/test/java/com/jsyn/unitgen/TestDelay.java create mode 100644 src/test/java/com/jsyn/unitgen/TestEnable.java create mode 100644 src/test/java/com/jsyn/unitgen/TestEnvelopeAttackDecay.java create mode 100644 src/test/java/com/jsyn/unitgen/TestEnvelopeDAHDSR.java create mode 100644 src/test/java/com/jsyn/unitgen/TestFunction.java create mode 100644 src/test/java/com/jsyn/unitgen/TestMath.java create mode 100644 src/test/java/com/jsyn/unitgen/TestRamps.java create mode 100644 src/test/java/com/jsyn/unitgen/TestUnitGate.java create mode 100644 src/test/java/com/jsyn/util/DebugSampleLoader.java create mode 100644 src/test/java/com/jsyn/util/TestFFT.java create mode 100644 src/test/java/com/jsyn/util/TestPseudoRandom.java create mode 100644 src/test/java/com/jsyn/util/TestVoiceAllocator.java delete mode 100644 tests/com/jsyn/SynthTestSuite.java delete mode 100644 tests/com/jsyn/benchmarks/BenchJSyn.java delete mode 100644 tests/com/jsyn/data/TestShortSample.java delete mode 100644 tests/com/jsyn/engine/TestAudioOutput.java delete mode 100644 tests/com/jsyn/engine/TestDevices.java delete mode 100644 tests/com/jsyn/engine/TestEngine.java delete mode 100644 tests/com/jsyn/engine/TestFifo.java delete mode 100644 tests/com/jsyn/engine/TestWaveFileReadWrite.java delete mode 100644 tests/com/jsyn/examples/AudioPassThrough.java delete mode 100644 tests/com/jsyn/examples/ChebyshevSong.java delete mode 100644 tests/com/jsyn/examples/CircuitTester.java delete mode 100644 tests/com/jsyn/examples/CustomCubeUnit.java delete mode 100644 tests/com/jsyn/examples/DualOscilloscope.java delete mode 100644 tests/com/jsyn/examples/EditEnvelope1.java delete mode 100644 tests/com/jsyn/examples/FFTPassthrough.java delete mode 100644 tests/com/jsyn/examples/GoogleWaveOscillator.java delete mode 100644 tests/com/jsyn/examples/HearDAHDSR.java delete mode 100644 tests/com/jsyn/examples/HearMoogFilter.java delete mode 100644 tests/com/jsyn/examples/HearSinePM.java delete mode 100644 tests/com/jsyn/examples/HearSpectralFilter.java delete mode 100644 tests/com/jsyn/examples/ListAudioDevices.java delete mode 100644 tests/com/jsyn/examples/LongEcho.java delete mode 100644 tests/com/jsyn/examples/MonoPassThrough.java delete mode 100644 tests/com/jsyn/examples/NotesToTone.java delete mode 100644 tests/com/jsyn/examples/PlayChords.java delete mode 100644 tests/com/jsyn/examples/PlayCustomUnit.java delete mode 100644 tests/com/jsyn/examples/PlayFunction.java delete mode 100644 tests/com/jsyn/examples/PlayGrains.java delete mode 100644 tests/com/jsyn/examples/PlayMIDI.java delete mode 100644 tests/com/jsyn/examples/PlayNotes.java delete mode 100644 tests/com/jsyn/examples/PlayPartials.java delete mode 100644 tests/com/jsyn/examples/PlaySample.java delete mode 100644 tests/com/jsyn/examples/PlaySampleCrossfade.java delete mode 100644 tests/com/jsyn/examples/PlaySampleWaveShaper.java delete mode 100644 tests/com/jsyn/examples/PlaySegmentedEnvelope.java delete mode 100644 tests/com/jsyn/examples/PlaySegmentedEnvelopeCallback.java delete mode 100644 tests/com/jsyn/examples/PlaySequence.java delete mode 100644 tests/com/jsyn/examples/PlayTone.java delete mode 100644 tests/com/jsyn/examples/RecordSineSweep.java delete mode 100644 tests/com/jsyn/examples/SampleHoldNoteBlaster.java delete mode 100644 tests/com/jsyn/examples/SawFaders.java delete mode 100644 tests/com/jsyn/examples/SeeGoogleWave.java delete mode 100644 tests/com/jsyn/examples/SeeOscillators.java delete mode 100644 tests/com/jsyn/examples/ShowWaves.java delete mode 100644 tests/com/jsyn/examples/SwarmOfOscillators.java delete mode 100644 tests/com/jsyn/examples/UseMidiKeyboard.java delete mode 100644 tests/com/jsyn/examples/WindCircuit.java delete mode 100644 tests/com/jsyn/midi/TestMidiLoop.java delete mode 100644 tests/com/jsyn/ports/TestQueuedDataPort.java delete mode 100644 tests/com/jsyn/ports/TestSequentialData.java delete mode 100644 tests/com/jsyn/ports/TestSet.java delete mode 100644 tests/com/jsyn/research/BenchMultiThreading.java delete mode 100644 tests/com/jsyn/research/RecordVariousRamps.java delete mode 100644 tests/com/jsyn/research/lambdas/LambdaUnits.java delete mode 100644 tests/com/jsyn/swing/TestRangeModels.java delete mode 100644 tests/com/jsyn/unitgen/CalibrateMoogFilter.java delete mode 100644 tests/com/jsyn/unitgen/EnablingGate.java delete mode 100644 tests/com/jsyn/unitgen/NonRealTimeTestCase.java delete mode 100644 tests/com/jsyn/unitgen/RecordMoogFilter.java delete mode 100644 tests/com/jsyn/unitgen/TestConnections.java delete mode 100644 tests/com/jsyn/unitgen/TestDelay.java delete mode 100644 tests/com/jsyn/unitgen/TestEnable.java delete mode 100644 tests/com/jsyn/unitgen/TestEnvelopeAttackDecay.java delete mode 100644 tests/com/jsyn/unitgen/TestEnvelopeDAHDSR.java delete mode 100644 tests/com/jsyn/unitgen/TestFunction.java delete mode 100644 tests/com/jsyn/unitgen/TestMath.java delete mode 100644 tests/com/jsyn/unitgen/TestRamps.java delete mode 100644 tests/com/jsyn/unitgen/TestUnitGate.java delete mode 100644 tests/com/jsyn/util/DebugSampleLoader.java delete mode 100644 tests/com/jsyn/util/TestFFT.java delete mode 100644 tests/com/jsyn/util/TestPseudoRandom.java delete mode 100644 tests/com/jsyn/util/TestVoiceAllocator.java diff --git a/.classpath b/.classpath deleted file mode 100644 index bb47eaa..0000000 --- a/.classpath +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/.gitignore b/.gitignore index 7a727e2..1e83f69 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,10 @@ -# ignore Eclipse and Ant generated directories -/build/ -/bin/ -/dist/ -/doc/ +build/ +.gradle/ +# Ignore IDE data to push towards a universally Gradle controlled environment +.idea/ +.classpath +.project + +# A temporary file sometimes generated +temp_recording.wav diff --git a/.project b/.project deleted file mode 100644 index 2ba0678..0000000 --- a/.project +++ /dev/null @@ -1,17 +0,0 @@ - - - JSyn - - - - - - org.eclipse.jdt.core.javabuilder - - - - - - org.eclipse.jdt.core.javanature - - diff --git a/DEVELOPING.md b/DEVELOPING.md new file mode 100644 index 0000000..5dc7202 --- /dev/null +++ b/DEVELOPING.md @@ -0,0 +1,44 @@ +## Developing + +With the new inclusion of Gradle replacing ant (bye bye 2014) this brings some more modern features. Running any of the commands will automatically install the Gradle wrapper, no other install needed. This uses Gradle 6.5.1 and Java 11. + +### Run The Demo + +``` +./gradlew run +``` + +### Javadoc generation + +``` +./gradlew javadoc +``` + +### Building Jar + +``` +./gradlew shadowJar +``` + +### Publish To Maven Local + +This allows you to use JSyn as a maven/gradle repository on your machine. + +``` +./gradlew publishToMavenLocal +``` + +It can be used via + +```groovy +repositories { + // ... + mavenLocal() +} + +dependencies { + // ... + implementation 'com.jsyn:jsyn:17.0.0-SNAPSHOT' +} +``` + diff --git a/README.txt b/README.txt deleted file mode 100644 index 96dc92f..0000000 --- a/README.txt +++ /dev/null @@ -1 +0,0 @@ -See README.md diff --git a/android/README.md b/android/README.md new file mode 100644 index 0000000..95a6a56 --- /dev/null +++ b/android/README.md @@ -0,0 +1,2 @@ +I'm not quite sure where to place this file. This has Android dependencies and from what I can tell was previously floating in this unrelated folder named `android`. From what I can tell, the intent is to place this class into your Android project when using it. + diff --git a/android/com/jsyn/devices/android/AndroidAudioForJSyn.java b/android/com/jsyn/devices/android/AndroidAudioForJSyn.java index 3aba1e4..20bbb68 100644 --- a/android/com/jsyn/devices/android/AndroidAudioForJSyn.java +++ b/android/com/jsyn/devices/android/AndroidAudioForJSyn.java @@ -84,9 +84,9 @@ public class AndroidAudioForJSyn implements AudioDeviceManager { minBufferSize = AudioTrack.getMinBufferSize(frameRate, AudioFormat.CHANNEL_OUT_STEREO, AudioFormat.ENCODING_PCM_FLOAT); - System.out.println("Audio minBufferSize = " + minBufferSize); + LOGGER.debug("Audio minBufferSize = " + minBufferSize); bufferSize = (3 * (minBufferSize / 2)) & ~3; - System.out.println("Audio bufferSize = " + bufferSize); + LOGGER.debug("Audio bufferSize = " + bufferSize); audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, frameRate, AudioFormat.CHANNEL_OUT_STEREO, AudioFormat.ENCODING_PCM_FLOAT, bufferSize, diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..047aba8 --- /dev/null +++ b/build.gradle @@ -0,0 +1,60 @@ +plugins { + id 'java' + id 'application' + id 'maven-publish' + id 'com.github.johnrengelman.shadow' version '6.0.0' +} + +group = 'com.jsyn' +version = '17.0.0-SNAPSHOT' +sourceCompatibility = '11' +mainClassName = 'com.jsyn.apps.AboutJSyn' + +repositories { + mavenCentral() + mavenLocal() +} + +dependencies { + testImplementation 'org.junit.jupiter:junit-jupiter:5.4.2' + + implementation 'org.slf4j:slf4j-api:1.7.25' + implementation 'org.slf4j:slf4j-log4j12:1.7.25' + + implementation fileTree(dir: 'libs', include: '*.jar') +} + +publishing { + publications { + mavenJava(MavenPublication) { + from components.java + } + } + repositories { + mavenLocal() + } +} + +jar { + manifest { + attributes('Main-Class': mainClassName) + } +} +shadowJar { + archiveFileName = "jysn-${version}.jar"; +} + +javadoc { + exclude 'com/portaudio/**' + exclude 'com/jsyn/devices/jportaudio/**' + source = sourceSets.main.allJava + + options { + addStringOption('Xdoclint:none', '-quiet') + } +} + +test { + useJUnitPlatform() +} + diff --git a/build.xml b/build.xml deleted file mode 100644 index e436ef2..0000000 --- a/build.xml +++ /dev/null @@ -1,66 +0,0 @@ - - - Build JSyn Java Synthesizer - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/build.gradle b/examples/build.gradle new file mode 100644 index 0000000..b3a9ef1 --- /dev/null +++ b/examples/build.gradle @@ -0,0 +1,14 @@ +apply plugin: 'java' + +version = '17.0.0-SNAPSHOT' + +repositories { + mavenCentral() +} + +dependencies { + implementation project(':') + + implementation 'org.slf4j:slf4j-api:1.7.25' + implementation 'org.slf4j:slf4j-log4j12:1.7.25' +} diff --git a/examples/src/main/java/com/jsyn/examples/AudioPassThrough.java b/examples/src/main/java/com/jsyn/examples/AudioPassThrough.java new file mode 100644 index 0000000..383dba4 --- /dev/null +++ b/examples/src/main/java/com/jsyn/examples/AudioPassThrough.java @@ -0,0 +1,74 @@ +/* + * Copyright 2010 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.examples; + +import com.jsyn.JSyn; +import com.jsyn.devices.AudioDeviceManager; +import com.jsyn.unitgen.LineIn; +import com.jsyn.unitgen.LineOut; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Pass audio input to audio output. + * + * @author Phil Burk (C) 2010 Mobileer Inc + */ +public class AudioPassThrough { + + private static final Logger LOGGER = LoggerFactory.getLogger(AudioPassThrough.class); + + private void test() { + LineIn lineIn; + LineOut lineOut; + + // Create a context for the synthesizer. + var synth = JSyn.createSynthesizer(); + // Add an audio input. + synth.add(lineIn = new LineIn()); + // Add an audio output. + synth.add(lineOut = new LineOut()); + // Connect the input to the output. + lineIn.output.connect(0, lineOut.input, 0); + lineIn.output.connect(1, lineOut.input, 1); + + // Both stereo. + int numInputChannels = 2; + int numOutputChannels = 2; + synth.start(44100, AudioDeviceManager.USE_DEFAULT_DEVICE, numInputChannels, + AudioDeviceManager.USE_DEFAULT_DEVICE, numOutputChannels); + + // We only need to start the LineOut. It will pull data from the LineIn. + lineOut.start(); + LOGGER.debug("Audio passthrough started."); + // Sleep a while. + try { + double time = synth.getCurrentTime(); + // Sleep for a few seconds. + synth.sleepUntil(time + 8.0); + } catch (InterruptedException e) { + e.printStackTrace(); + } + // Stop everything. + synth.stop(); + LOGGER.debug("All done."); + } + + public static void main(String[] args) { + new AudioPassThrough().test(); + } +} diff --git a/examples/src/main/java/com/jsyn/examples/ChebyshevSong.java b/examples/src/main/java/com/jsyn/examples/ChebyshevSong.java new file mode 100644 index 0000000..6acd894 --- /dev/null +++ b/examples/src/main/java/com/jsyn/examples/ChebyshevSong.java @@ -0,0 +1,185 @@ +/* + * Copyright 2010 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.examples; + +import java.awt.BorderLayout; + +import javax.swing.JApplet; + +import com.jsyn.JSyn; +import com.jsyn.Synthesizer; +import com.jsyn.instruments.WaveShapingVoice; +import com.jsyn.scope.AudioScope; +import com.jsyn.swing.JAppletFrame; +import com.jsyn.unitgen.Add; +import com.jsyn.unitgen.LineOut; +import com.jsyn.util.PseudoRandom; +import com.jsyn.util.VoiceAllocator; +import com.softsynth.math.AudioMath; +import com.softsynth.shared.time.TimeStamp; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/*************************************************************** + * Play notes using a WaveShapingVoice. Allocate the notes using a VoiceAllocator. + * + * @author Phil Burk (C) 2010 Mobileer Inc + */ +public class ChebyshevSong extends JApplet implements Runnable { + + private static final Logger LOGGER = LoggerFactory.getLogger(ChebyshevSong.class); + + private Synthesizer synth; + private Add mixer; + private LineOut lineOut; + private AudioScope scope; + private volatile boolean go = false; + private PseudoRandom pseudo = new PseudoRandom(); + private final static int MAX_VOICES = 8; + private final static int MAX_NOTES = 5; + private VoiceAllocator allocator; + private final static int scale[] = { + 0, 2, 4, 7, 9 + }; // pentatonic scale + + /* Can be run as either an application or as an applet. */ + public static void main(String[] args) { + ChebyshevSong applet = new ChebyshevSong(); + JAppletFrame frame = new JAppletFrame("ChebyshevSong", applet); + frame.setSize(640, 300); + frame.setVisible(true); + frame.test(); + } + + /* + * Setup synthesis. + */ + @Override + public void start() { + setLayout(new BorderLayout()); + + synth = JSyn.createSynthesizer(); + + // Use a submix so we can show it on the scope. + synth.add(mixer = new Add()); + synth.add(lineOut = new LineOut()); + + mixer.output.connect(0, lineOut.input, 0); + mixer.output.connect(0, lineOut.input, 1); + + WaveShapingVoice[] voices = new WaveShapingVoice[MAX_VOICES]; + for (int i = 0; i < MAX_VOICES; i++) { + WaveShapingVoice voice = new WaveShapingVoice(); + synth.add(voice); + voice.usePreset(0); + voice.getOutput().connect(mixer.inputA); + voices[i] = voice; + } + allocator = new VoiceAllocator(voices); + + // Start synthesizer using default stereo output at 44100 Hz. + synth.start(); + lineOut.start(); + + // Use a scope to show the mixed output. + scope = new AudioScope(synth); + scope.addProbe(mixer.output); + scope.setTriggerMode(AudioScope.TriggerMode.NORMAL); + scope.getView().setControlsVisible(false); + add(BorderLayout.CENTER, scope.getView()); + scope.start(); + + /* Synchronize Java display. */ + getParent().validate(); + getToolkit().sync(); + + // start thread that plays notes + Thread thread = new Thread(this); + go = true; + thread.start(); + + } + + @Override + public void stop() { + // tell song thread to finish + go = false; + removeAll(); + synth.stop(); + } + + double indexToFrequency(int index) { + int octave = index / scale.length; + int temp = index % scale.length; + int pitch = scale[temp] + (12 * octave); + return AudioMath.pitchToFrequency(pitch + 16); + } + + private void noteOff(double time, int noteNumber) { + allocator.noteOff(noteNumber, new TimeStamp(time)); + } + + private void noteOn(double time, int noteNumber) { + double frequency = indexToFrequency(noteNumber); + double amplitude = 0.1; + TimeStamp timeStamp = new TimeStamp(time); + allocator.noteOn(noteNumber, frequency, amplitude, timeStamp); + allocator.setPort(noteNumber, "Range", 0.7, synth.createTimeStamp()); + } + + @Override + public void run() { + // always choose a new song based on time&date + int savedSeed = (int) System.currentTimeMillis(); + // calculate tempo + double duration = 0.2; + // set time ahead of any system latency + double advanceTime = 0.5; + // time for next note to start + double nextTime = synth.getCurrentTime() + advanceTime; + // note is ON for half the duration + double onTime = duration / 2; + int beatIndex = 0; + try { + do { + // on every measure, maybe repeat previous pattern + if ((beatIndex & 7) == 0) { + if ((Math.random() < (1.0 / 2.0))) + pseudo.setSeed(savedSeed); + else if ((Math.random() < (1.0 / 2.0))) + savedSeed = pseudo.getSeed(); + } + + // Play a bunch of random notes in the scale. + int numNotes = pseudo.choose(MAX_NOTES); + for (int i = 0; i < numNotes; i++) { + int noteNumber = pseudo.choose(30); + noteOn(nextTime, noteNumber); + noteOff(nextTime + onTime, noteNumber); + } + + nextTime += duration; + beatIndex += 1; + + // wake up before we need to play note to cover system latency + synth.sleepUntil(nextTime - advanceTime); + } while (go); + } catch (InterruptedException e) { + LOGGER.error("Song exiting", e); + } + } +} diff --git a/examples/src/main/java/com/jsyn/examples/CircuitTester.java b/examples/src/main/java/com/jsyn/examples/CircuitTester.java new file mode 100644 index 0000000..0a429fb --- /dev/null +++ b/examples/src/main/java/com/jsyn/examples/CircuitTester.java @@ -0,0 +1,111 @@ +/* + * Copyright 2012 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.examples; + +import java.awt.BorderLayout; + +import javax.swing.JApplet; + +import com.jsyn.JSyn; +import com.jsyn.Synthesizer; +import com.jsyn.instruments.DualOscillatorSynthVoice; +import com.jsyn.scope.AudioScope; +import com.jsyn.swing.JAppletFrame; +import com.jsyn.swing.SoundTweaker; +import com.jsyn.unitgen.LineOut; +import com.jsyn.unitgen.UnitSource; + +/** + * Listen to a circuit while tweaking it knobs. Show output in a scope. + * + * @author Phil Burk (C) 2012 Mobileer Inc + */ +public class CircuitTester extends JApplet { + private Synthesizer synth; + private LineOut lineOut; + private SoundTweaker tweaker; + private UnitSource unitSource; + private AudioScope scope; + + @Override + public void init() { + setLayout(new BorderLayout()); + + synth = JSyn.createSynthesizer(); + synth.add(lineOut = new LineOut()); + + unitSource = createUnitSource(); + synth.add(unitSource.getUnitGenerator()); + + // Connect the source to both left and right speakers. + unitSource.getOutput().connect(0, lineOut.input, 0); + unitSource.getOutput().connect(0, lineOut.input, 1); + + tweaker = new SoundTweaker(synth, unitSource.getUnitGenerator().getClass().getName(), + unitSource); + add(tweaker, BorderLayout.CENTER); + + // Use a scope to see the output. + scope = new AudioScope(synth); + scope.addProbe(unitSource.getOutput()); + scope.setTriggerMode(AudioScope.TriggerMode.AUTO); + scope.getView().setControlsVisible(false); + add(BorderLayout.SOUTH, scope.getView()); + + validate(); + } + + /** + * Override this to test your own circuits. + * + * @return + */ + public UnitSource createUnitSource() { + //return new SampleHoldNoteBlaster(); + //return new com.syntona.exported.FMVoice(); + return new DualOscillatorSynthVoice(); + //return new WindCircuit(); + //return new WhiteNoise(); + //return new BrownNoise(); + } + + @Override + public void start() { + // Start synthesizer using default stereo output at 44100 Hz. + synth.start(); + // Start the LineOut. It will pull data from the other units. + lineOut.start(); + + scope.start(); + } + + @Override + public void stop() { + scope.stop(); + synth.stop(); + } + + /* Can be run as either an application or as an applet. */ + public static void main(String[] args) { + CircuitTester applet = new CircuitTester(); + JAppletFrame frame = new JAppletFrame("JSyn Circuit Tester", applet); + frame.setSize(600, 600); + frame.setVisible(true); + frame.test(); + } + +} diff --git a/examples/src/main/java/com/jsyn/examples/CustomCubeUnit.java b/examples/src/main/java/com/jsyn/examples/CustomCubeUnit.java new file mode 100644 index 0000000..897052c --- /dev/null +++ b/examples/src/main/java/com/jsyn/examples/CustomCubeUnit.java @@ -0,0 +1,48 @@ +/* + * Copyright 2010 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.examples; + +import com.jsyn.unitgen.UnitFilter; + +/** + * Custom unit generator that can be used with other JSyn units. Cube the input value and write it + * to output port. + * + * @author Phil Burk (C) 2010 Mobileer Inc + */ +public class CustomCubeUnit extends UnitFilter { + + /** This is where the synthesis occurs. + * It is called in a high priority background thread so do not do + * anything crazy here like reading a file or doing network I/O. + * Just do fast arithmetic. + *
+ * The start and limit allow us to do either block or single sample processing. + */ + @Override + public void generate(int start, int limit) { + // Get signal arrays from ports. + double[] inputs = input.getValues(); + double[] outputs = output.getValues(); + + for (int i = start; i < limit; i++) { + double x = inputs[i]; + // Do the math. + outputs[i] = x * x * x; + } + } +} diff --git a/examples/src/main/java/com/jsyn/examples/DualOscilloscope.java b/examples/src/main/java/com/jsyn/examples/DualOscilloscope.java new file mode 100644 index 0000000..2c4caa3 --- /dev/null +++ b/examples/src/main/java/com/jsyn/examples/DualOscilloscope.java @@ -0,0 +1,163 @@ +/* + * Copyright 2012 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.examples; + +import java.awt.BorderLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.ArrayList; + +import javax.swing.JApplet; +import javax.swing.JComboBox; + +import com.jsyn.JSyn; +import com.jsyn.Synthesizer; +import com.jsyn.devices.AudioDeviceFactory; +import com.jsyn.devices.AudioDeviceManager; +import com.jsyn.scope.AudioScope; +import com.jsyn.swing.JAppletFrame; +import com.jsyn.unitgen.ChannelIn; +import com.jsyn.unitgen.PassThrough; + +/** + * Two channel oscilloscope that demonstrates the use of audio input. + * + * @author Phil Burk (C) 2012 Mobileer Inc + */ +public class DualOscilloscope extends JApplet { + private Synthesizer synth; + private ChannelIn channel1; + private ChannelIn channel2; + private PassThrough pass1; + private PassThrough pass2; + private AudioScope scope; + private AudioDeviceManager audioManager; + private int defaultInputId; + private ArrayList deviceNames = new ArrayList(); + private ArrayList deviceMaxInputs = new ArrayList(); + private ArrayList deviceIds = new ArrayList(); + private int defaultSelection; + private JComboBox deviceComboBox; + + @Override + public void init() { + audioManager = AudioDeviceFactory.createAudioDeviceManager(true); + synth = JSyn.createSynthesizer(audioManager); + + int numDevices = audioManager.getDeviceCount(); + defaultInputId = audioManager.getDefaultInputDeviceID(); + for (int i = 0; i < numDevices; i++) { + int maxInputs = audioManager.getMaxInputChannels(i); + if (maxInputs > 0) { + String deviceName = audioManager.getDeviceName(i); + String itemName = maxInputs + ", " + deviceName + " (#" + i + ")"; + if (i == defaultInputId) { + defaultSelection = deviceNames.size(); + itemName += " (Default)"; + } + deviceNames.add(itemName); + deviceMaxInputs.add(maxInputs); + deviceIds.add(i); + } + } + + synth.add(channel1 = new ChannelIn()); + channel1.setChannelIndex(0); + synth.add(channel2 = new ChannelIn()); + channel2.setChannelIndex(1); + + // Use PassThrough so we can easily disconnect input channels from the scope. + synth.add(pass1 = new PassThrough()); + synth.add(pass2 = new PassThrough()); + + setupGUI(); + } + + private void setupGUI() { + setLayout(new BorderLayout()); + + deviceComboBox = new JComboBox(deviceNames.toArray(new String[0])); + deviceComboBox.setSelectedIndex(defaultSelection); + add(deviceComboBox, BorderLayout.NORTH); + deviceComboBox.addActionListener(new ActionListener() { + + @Override + public void actionPerformed(ActionEvent e) { + stopAudio(); + int itemIndex = deviceComboBox.getSelectedIndex(); + startAudio(itemIndex); + } + }); + + scope = new AudioScope(synth); + + scope.addProbe(pass1.output); + scope.addProbe(pass2.output); + + scope.setTriggerMode(AudioScope.TriggerMode.AUTO); + scope.getView().setControlsVisible(true); + add(scope.getView(), BorderLayout.CENTER); + validate(); + } + + protected void startAudio(int itemIndex) { + // Both stereo. + int numInputChannels = deviceMaxInputs.get(itemIndex); + if (numInputChannels > 2) + numInputChannels = 2; + int inputDeviceIndex = deviceIds.get(itemIndex); + synth.start(44100, inputDeviceIndex, numInputChannels, + AudioDeviceManager.USE_DEFAULT_DEVICE, 0); + + channel1.output.connect(pass1.input); + // Only connect second channel if more than one input channel. + if (numInputChannels > 1) { + channel2.output.connect(pass2.input); + } + + // We only need to start the LineOut. It will pull data from the + // channels. + scope.start(); + } + + @Override + public void start() { + startAudio(defaultSelection); + } + + public void stopAudio() { + pass1.input.disconnectAll(); + pass2.input.disconnectAll(); + scope.stop(); + synth.stop(); + } + + @Override + public void stop() { + stopAudio(); + } + + /* Can be run as either an application or as an applet. */ + public static void main(String[] args) { + DualOscilloscope applet = new DualOscilloscope(); + JAppletFrame frame = new JAppletFrame("Dual Oscilloscope", applet); + frame.setSize(640, 400); + frame.setVisible(true); + frame.test(); + } + +} diff --git a/examples/src/main/java/com/jsyn/examples/EditEnvelope1.java b/examples/src/main/java/com/jsyn/examples/EditEnvelope1.java new file mode 100644 index 0000000..997b8b4 --- /dev/null +++ b/examples/src/main/java/com/jsyn/examples/EditEnvelope1.java @@ -0,0 +1,148 @@ +/* + * Copyright 1997 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. + */ +/** + * Test Envelope using Java Audio Synthesizer + * Trigger attack or release portion. + * + * @author (C) 1997 Phil Burk + */ + +package com.jsyn.examples; + +import java.awt.BorderLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +import javax.swing.JApplet; +import javax.swing.JButton; +import javax.swing.JPanel; + +import com.jsyn.JSyn; +import com.jsyn.Synthesizer; +import com.jsyn.data.SegmentedEnvelope; +import com.jsyn.swing.EnvelopeEditorPanel; +import com.jsyn.swing.EnvelopePoints; +import com.jsyn.swing.JAppletFrame; +import com.jsyn.unitgen.LineOut; +import com.jsyn.unitgen.SawtoothOscillatorBL; +import com.jsyn.unitgen.UnitOscillator; +import com.jsyn.unitgen.VariableRateDataReader; +import com.jsyn.unitgen.VariableRateMonoReader; + +public class EditEnvelope1 extends JApplet { + private Synthesizer synth; + private UnitOscillator osc; + private LineOut lineOut; + private SegmentedEnvelope envelope; + private VariableRateDataReader envelopePlayer; + + final int MAX_FRAMES = 16; + JButton hitme; + JButton attackButton; + JButton releaseButton; + private EnvelopeEditorPanel envEditor; + private EnvelopePoints points; + + /* Can be run as either an application or as an applet. */ + public static void main(String[] args) { + EditEnvelope1 applet = new EditEnvelope1(); + JAppletFrame frame = new JAppletFrame("Test SynthEnvelope", applet); + frame.setSize(440, 200); + frame.setVisible(true); + frame.test(); + } + + /* + * Setup synthesis. + */ + @Override + public void start() { + setLayout(new BorderLayout()); + + // Create a context for the synthesizer. + synth = JSyn.createSynthesizer(); + // Add a tone generator. + synth.add(osc = new SawtoothOscillatorBL()); + // Add an envelope player. + synth.add(envelopePlayer = new VariableRateMonoReader()); + + envelope = new SegmentedEnvelope(MAX_FRAMES); + + // Add an output mixer. + synth.add(lineOut = new LineOut()); + envelopePlayer.output.connect(osc.amplitude); + // Connect the oscillator to the output. + osc.output.connect(0, lineOut.input, 0); + osc.output.connect(0, lineOut.input, 1); + + // Start synthesizer using default stereo output at 44100 Hz. + synth.start(); + + JPanel bottomPanel = new JPanel(); + bottomPanel.add(hitme = new JButton("On")); + hitme.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + points.updateEnvelopeIfDirty(envelope); + envelopePlayer.dataQueue.queueOn(envelope); + } + }); + + bottomPanel.add(attackButton = new JButton("Off")); + attackButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + points.updateEnvelopeIfDirty(envelope); + envelopePlayer.dataQueue.queueOff(envelope); + } + }); + + bottomPanel.add(releaseButton = new JButton("Queue")); + releaseButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + points.updateEnvelopeIfDirty(envelope); + envelopePlayer.dataQueue.queue(envelope); + } + }); + + add(bottomPanel, BorderLayout.SOUTH); + lineOut.start(); + + // Create vector of points for editor. + points = new EnvelopePoints(); + points.setName(osc.amplitude.getName()); + + // Setup initial envelope shape. + points.add(0.5, 1.0); + points.add(0.5, 0.2); + points.add(0.5, 0.8); + points.add(0.5, 0.0); + points.updateEnvelope(envelope); + + // Add an envelope editor to the center of the panel. + add("Center", envEditor = new EnvelopeEditorPanel(points, MAX_FRAMES)); + + /* Synchronize Java display. */ + getParent().validate(); + getToolkit().sync(); + } + + @Override + public void stop() { + synth.stop(); + } +} diff --git a/examples/src/main/java/com/jsyn/examples/FFTPassthrough.java b/examples/src/main/java/com/jsyn/examples/FFTPassthrough.java new file mode 100644 index 0000000..e4b72c8 --- /dev/null +++ b/examples/src/main/java/com/jsyn/examples/FFTPassthrough.java @@ -0,0 +1,105 @@ +/* + * Copyright 2010 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.examples; + +import com.jsyn.JSyn; +import com.jsyn.Synthesizer; +import com.jsyn.unitgen.LineOut; +import com.jsyn.unitgen.PassThrough; +import com.jsyn.unitgen.SineOscillator; +import com.jsyn.unitgen.SpectralFFT; +import com.jsyn.unitgen.SpectralIFFT; +import com.jsyn.unitgen.UnitOscillator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Play a sine sweep through an FFT/IFFT pair. + * + * @author Phil Burk (C) 2010 Mobileer Inc + */ +public class FFTPassthrough { + + private static final Logger LOGGER = LoggerFactory.getLogger(FFTPassthrough.class); + + private Synthesizer synth; + private PassThrough center; + private UnitOscillator osc; + private UnitOscillator lfo; + private SpectralFFT fft; + private SpectralIFFT ifft1; + private LineOut lineOut; + private SpectralIFFT ifft2; + + private void test() { + // Create a context for the synthesizer. + synth = JSyn.createSynthesizer(); + + // Start synthesizer using default stereo output at 44100 Hz. + synth.start(); + + // Add a tone generator. + synth.add(center = new PassThrough()); + // synth.add( osc = new SawtoothOscillatorBL() ); + synth.add(osc = new SineOscillator()); + synth.add(lfo = new SineOscillator()); + synth.add(fft = new SpectralFFT()); + synth.add(ifft1 = new SpectralIFFT()); + synth.add(ifft2 = new SpectralIFFT()); + // Add a stereo audio output unit. + synth.add(lineOut = new LineOut()); + + // Connect the oscillator to both channels of the output. + center.output.connect(osc.frequency); + lfo.output.connect(osc.frequency); + osc.output.connect(fft.input); + fft.output.connect(ifft1.input); + fft.output.connect(ifft2.input); + ifft1.output.connect(0, lineOut.input, 0); + ifft2.output.connect(0, lineOut.input, 1); + + // Set the frequency and amplitude for the modulated sine wave. + center.input.set(600.0); + lfo.frequency.set(0.2); + lfo.amplitude.set(400.0); + osc.amplitude.set(0.6); + + // We only need to start the LineOut. It will pull data through the + // chain. + lineOut.start(); + + LOGGER.debug("You should now be hearing a clean oscillator on the left channel,"); + LOGGER.debug("and the FFT->IFFT processed signal on the right channel."); + + // Sleep while the sound is generated in the background. + try { + double time = synth.getCurrentTime(); + // Sleep for a few seconds. + synth.sleepUntil(time + 20.0); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + LOGGER.debug("Stop playing. -------------------"); + // Stop everything. + synth.stop(); + } + + public static void main(String[] args) { + new FFTPassthrough().test(); + } +} diff --git a/examples/src/main/java/com/jsyn/examples/GoogleWaveOscillator.java b/examples/src/main/java/com/jsyn/examples/GoogleWaveOscillator.java new file mode 100644 index 0000000..cf76298 --- /dev/null +++ b/examples/src/main/java/com/jsyn/examples/GoogleWaveOscillator.java @@ -0,0 +1,72 @@ +/* + * Copyright 2010 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.examples; + +import com.jsyn.ports.UnitInputPort; +import com.jsyn.unitgen.UnitOscillator; + +/** + * Custom unit generator to create the waveform shown on the Google home page on 2/22/12. + * + * @author Phil Burk (C) 2010 Mobileer Inc + */ +public class GoogleWaveOscillator extends UnitOscillator { + public UnitInputPort variance; + private double phaseIncrement = 0.1; + private double previousY; + private double randomAmplitude = 0.0; + + public GoogleWaveOscillator() { + addPort(variance = new UnitInputPort("Variance", 0.0)); + } + + @Override + public void generate(int start, int limit) { + // Get signal arrays from ports. + double[] freqs = frequency.getValues(); + double[] outputs = output.getValues(); + double currentPhase = phase.getValue(); + double y; + + for (int i = start; i < limit; i++) { + if (currentPhase > 0.0) { + y = Math.sqrt(4.0 * (currentPhase * (1.0 - currentPhase))); + } else { + double p = -currentPhase; + y = -Math.sqrt(4.0 * (p * (1.0 - p))); + } + + if ((previousY * y) <= 0.0) { + // Calculate randomly offset phaseIncrement. + double v = variance.getValues()[0]; + double range = ((Math.random() - 0.5) * 4.0 * v); + double scale = Math.pow(2.0, range); + phaseIncrement = convertFrequencyToPhaseIncrement(freqs[i]) * scale; + + // Calculate random amplitude. + scale = 1.0 + ((Math.random() - 0.5) * 1.5 * v); + randomAmplitude = amplitude.getValues()[0] * scale; + } + + outputs[i] = y * randomAmplitude; + previousY = y; + + currentPhase = incrementWrapPhase(currentPhase, phaseIncrement); + } + phase.setValue(currentPhase); + } +} diff --git a/examples/src/main/java/com/jsyn/examples/HearDAHDSR.java b/examples/src/main/java/com/jsyn/examples/HearDAHDSR.java new file mode 100644 index 0000000..a6c03aa --- /dev/null +++ b/examples/src/main/java/com/jsyn/examples/HearDAHDSR.java @@ -0,0 +1,129 @@ +/* + * Copyright 2010 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.examples; + +import java.awt.GridLayout; + +import javax.swing.BorderFactory; +import javax.swing.JApplet; + +import com.jsyn.JSyn; +import com.jsyn.Synthesizer; +import com.jsyn.ports.UnitInputPort; +import com.jsyn.swing.DoubleBoundedRangeModel; +import com.jsyn.swing.JAppletFrame; +import com.jsyn.swing.PortModelFactory; +import com.jsyn.swing.RotaryTextController; +import com.jsyn.unitgen.EnvelopeDAHDSR; +import com.jsyn.unitgen.LineOut; +import com.jsyn.unitgen.SineOscillator; +import com.jsyn.unitgen.SquareOscillator; +import com.jsyn.unitgen.UnitOscillator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Play a tone using a JSyn oscillator. Modulate the amplitude using a DAHDSR envelope. + * + * @author Phil Burk (C) 2010 Mobileer Inc + */ +public class HearDAHDSR extends JApplet { + + private static final Logger LOGGER = LoggerFactory.getLogger(HearDAHDSR.class); + + private Synthesizer synth; + private UnitOscillator osc; + // Use a square wave to trigger the envelope. + private UnitOscillator gatingOsc; + private EnvelopeDAHDSR dahdsr; + private LineOut lineOut; + + @Override + public void init() { + synth = JSyn.createSynthesizer(); + + // Add a tone generator. + synth.add(osc = new SineOscillator()); + // Add a trigger. + synth.add(gatingOsc = new SquareOscillator()); + // Use an envelope to control the amplitude. + synth.add(dahdsr = new EnvelopeDAHDSR()); + // Add an output mixer. + synth.add(lineOut = new LineOut()); + + gatingOsc.output.connect(dahdsr.input); + dahdsr.output.connect(osc.amplitude); + dahdsr.attack.setup(0.001, 0.01, 2.0); + osc.output.connect(0, lineOut.input, 0); + osc.output.connect(0, lineOut.input, 1); + + gatingOsc.frequency.setup(0.001, 0.5, 10.0); + gatingOsc.frequency.setName("Rate"); + + osc.frequency.setup(50.0, 440.0, 2000.0); + osc.frequency.setName("Freq"); + + // Arrange the knob in a row. + setLayout(new GridLayout(1, 0)); + + setupPortKnob(osc.frequency); + setupPortKnob(gatingOsc.frequency); + setupPortKnob(dahdsr.attack); + setupPortKnob(dahdsr.hold); + setupPortKnob(dahdsr.decay); + setupPortKnob(dahdsr.sustain); + setupPortKnob(dahdsr.release); + + validate(); + } + + private void setupPortKnob(UnitInputPort port) { + + DoubleBoundedRangeModel model = PortModelFactory.createExponentialModel(port); + LOGGER.debug("Make knob for " + port.getName() + ", model.getDV = " + + model.getDoubleValue() + ", model.getV = " + model.getValue() + ", port.getV = " + + port.get()); + RotaryTextController knob = new RotaryTextController(model, 10); + knob.setBorder(BorderFactory.createTitledBorder(port.getName())); + knob.setTitle(port.getName()); + add(knob); + } + + @Override + public void start() { + // Start synthesizer using default stereo output at 44100 Hz. + synth.start(); + // We only need to start the LineOut. It will pull data from the + // oscillator. + lineOut.start(); + } + + @Override + public void stop() { + synth.stop(); + } + + /* Can be run as either an application or as an applet. */ + public static void main(String[] args) { + HearDAHDSR applet = new HearDAHDSR(); + JAppletFrame frame = new JAppletFrame("Hear DAHDSR Envelope", applet); + frame.setSize(640, 200); + frame.setVisible(true); + frame.test(); + } + +} diff --git a/examples/src/main/java/com/jsyn/examples/HearMoogFilter.java b/examples/src/main/java/com/jsyn/examples/HearMoogFilter.java new file mode 100644 index 0000000..2444dd7 --- /dev/null +++ b/examples/src/main/java/com/jsyn/examples/HearMoogFilter.java @@ -0,0 +1,207 @@ +/* + * Copyright 2010 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.examples; + +import java.awt.BorderLayout; +import java.awt.GridLayout; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; + +import javax.swing.BorderFactory; +import javax.swing.BoxLayout; +import javax.swing.ButtonGroup; +import javax.swing.JApplet; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JRadioButton; + +import com.jsyn.JSyn; +import com.jsyn.Synthesizer; +import com.jsyn.ports.UnitInputPort; +import com.jsyn.scope.AudioScope; +import com.jsyn.swing.DoubleBoundedRangeModel; +import com.jsyn.swing.JAppletFrame; +import com.jsyn.swing.PortModelFactory; +import com.jsyn.swing.RotaryTextController; +import com.jsyn.unitgen.FilterFourPoles; +import com.jsyn.unitgen.FilterLowPass; +import com.jsyn.unitgen.LineOut; +import com.jsyn.unitgen.LinearRamp; +import com.jsyn.unitgen.PassThrough; +import com.jsyn.unitgen.SawtoothOscillatorBL; +import com.jsyn.unitgen.UnitOscillator; + +/** + * Play a sawtooth through a 4-pole filter. + * + * @author Phil Burk (C) 2010 Mobileer Inc + */ +public class HearMoogFilter extends JApplet { + private Synthesizer synth; + private UnitOscillator oscillator; + private FilterFourPoles filterMoog; + private FilterLowPass filterBiquad; + private LinearRamp rampCutoff; + private PassThrough tieQ; + private PassThrough tieCutoff; + private PassThrough mixer; + private LineOut lineOut; + + private AudioScope scope; + private boolean useCutoffRamp = false; + + @Override + public void init() { + synth = JSyn.createSynthesizer(); + synth.add(oscillator = new SawtoothOscillatorBL()); + synth.add(rampCutoff = new LinearRamp()); + synth.add(tieQ = new PassThrough()); + synth.add(tieCutoff = new PassThrough()); + synth.add(filterMoog = new FilterFourPoles()); + synth.add(filterBiquad = new FilterLowPass()); + synth.add(mixer = new PassThrough()); + synth.add(lineOut = new LineOut()); + + oscillator.output.connect(filterMoog.input); + oscillator.output.connect(filterBiquad.input); + if (useCutoffRamp) { + rampCutoff.output.connect(filterMoog.frequency); + rampCutoff.output.connect(filterBiquad.frequency); + rampCutoff.time.set(0.000); + } else { + tieCutoff.output.connect(filterMoog.frequency); + tieCutoff.output.connect(filterBiquad.frequency); + } + tieQ.output.connect(filterMoog.Q); + tieQ.output.connect(filterBiquad.Q); + filterMoog.output.connect(mixer.input); + mixer.output.connect(0, lineOut.input, 0); + mixer.output.connect(0, lineOut.input, 1); + + filterBiquad.amplitude.set(0.1); + oscillator.frequency.setup(50.0, 130.0, 3000.0); + oscillator.amplitude.setup(0.0, 0.336, 1.0); + rampCutoff.input.setup(filterMoog.frequency); + tieCutoff.input.setup(filterMoog.frequency); + tieQ.input.setup(0.1, 0.7, 10.0); + setupGUI(); + } + + private void setupGUI() { + setLayout(new BorderLayout()); + + add(new JLabel("Sawtooth through a \"Moog\" style filter."), BorderLayout.NORTH); + + JPanel rackPanel = new JPanel(); + rackPanel.setLayout(new BoxLayout(rackPanel, BoxLayout.Y_AXIS)); + + JPanel buttonPanel = new JPanel(); + ButtonGroup cbg = new ButtonGroup(); + JRadioButton radioButton = new JRadioButton("Moog", true); + cbg.add(radioButton); + radioButton.addItemListener(new ItemListener() { + @Override + public void itemStateChanged(ItemEvent e) { + mixer.input.disconnectAll(); + filterMoog.output.connect(mixer.input); + } + }); + buttonPanel.add(radioButton); + radioButton = new JRadioButton("Biquad", false); + cbg.add(radioButton); + radioButton.addItemListener(new ItemListener() { + @Override + public void itemStateChanged(ItemEvent e) { + mixer.input.disconnectAll(); + filterBiquad.output.connect(mixer.input); + } + }); + buttonPanel.add(radioButton); + + /* + * buttonPanel.add( new JLabel("Show:") ); cbg = new ButtonGroup(); radioButton = new + * JRadioButton( "Waveform", true ); cbg.add( radioButton ); radioButton.addItemListener( + * new ItemListener() { public void itemStateChanged( ItemEvent e ) { scope.setViewMode( + * AudioScope.ViewMode.WAVEFORM ); } } ); buttonPanel.add( radioButton ); radioButton = new + * JRadioButton( "Spectrum", true ); cbg.add( radioButton ); radioButton.addItemListener( + * new ItemListener() { public void itemStateChanged( ItemEvent e ) { scope.setViewMode( + * AudioScope.ViewMode.SPECTRUM ); } } ); buttonPanel.add( radioButton ); + */ + + rackPanel.add(buttonPanel); + + // Arrange the knobs in a row. + JPanel knobPanel = new JPanel(); + knobPanel.setLayout(new GridLayout(1, 0)); + + knobPanel.add(setupPortKnob(oscillator.frequency, "OscFreq")); + knobPanel.add(setupPortKnob(oscillator.amplitude, "OscAmp")); + + if (useCutoffRamp) { + knobPanel.add(setupPortKnob(rampCutoff.input, "Cutoff")); + } else { + knobPanel.add(setupPortKnob(tieCutoff.input, "Cutoff")); + } + knobPanel.add(setupPortKnob(tieQ.input, "Q")); + rackPanel.add(knobPanel); + add(rackPanel, BorderLayout.SOUTH); + + scope = new AudioScope(synth); + scope.addProbe(oscillator.output); + scope.addProbe(filterMoog.output); + scope.addProbe(filterBiquad.output); + scope.setTriggerMode(AudioScope.TriggerMode.NORMAL); + scope.getView().setControlsVisible(false); + add(scope.getView(), BorderLayout.CENTER); + scope.start(); + validate(); + } + + private RotaryTextController setupPortKnob(UnitInputPort port, String label) { + DoubleBoundedRangeModel model = PortModelFactory.createExponentialModel(port); + RotaryTextController knob = new RotaryTextController(model, 10); + knob.setBorder(BorderFactory.createTitledBorder(label)); + knob.setTitle(label); + return knob; + } + + @Override + public void start() { + // Start synthesizer using default stereo output at 44100 Hz. + synth.start(); + scope.start(); + // We only need to start the LineOut. It will pull data from the + // oscillator. + lineOut.start(); + } + + @Override + public void stop() { + scope.stop(); + synth.stop(); + } + + /* Can be run as either an application or as an applet. */ + public static void main(String[] args) { + HearMoogFilter applet = new HearMoogFilter(); + JAppletFrame frame = new JAppletFrame("Hear Moog Style Filter", applet); + frame.setSize(800, 600); + frame.setVisible(true); + frame.test(); + } + +} diff --git a/examples/src/main/java/com/jsyn/examples/HearSinePM.java b/examples/src/main/java/com/jsyn/examples/HearSinePM.java new file mode 100644 index 0000000..c567b03 --- /dev/null +++ b/examples/src/main/java/com/jsyn/examples/HearSinePM.java @@ -0,0 +1,128 @@ +/* + * Copyright 2010 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.examples; + +import java.awt.BorderLayout; +import java.awt.GridLayout; + +import javax.swing.BorderFactory; +import javax.swing.JApplet; +import javax.swing.JLabel; +import javax.swing.JPanel; + +import com.jsyn.JSyn; +import com.jsyn.Synthesizer; +import com.jsyn.ports.UnitInputPort; +import com.jsyn.scope.AudioScope; +import com.jsyn.swing.DoubleBoundedRangeModel; +import com.jsyn.swing.JAppletFrame; +import com.jsyn.swing.PortModelFactory; +import com.jsyn.swing.RotaryTextController; +import com.jsyn.unitgen.LineOut; +import com.jsyn.unitgen.SineOscillator; +import com.jsyn.unitgen.SineOscillatorPhaseModulated; + +/** + * Play a tone using a phase modulated sinewave oscillator. Phase modulation (PM) is very similar to + * frequency modulation (FM) but is easier to control. + * + * @author Phil Burk (C) 2010 Mobileer Inc + */ +public class HearSinePM extends JApplet { + private Synthesizer synth; + SineOscillatorPhaseModulated carrier; + SineOscillator modulator; + LineOut lineOut; + AudioScope scope; + + @Override + public void init() { + synth = JSyn.createSynthesizer(); + // Add a tone generator. + synth.add(modulator = new SineOscillator()); + // Add a trigger. + synth.add(carrier = new SineOscillatorPhaseModulated()); + // Add an output mixer. + synth.add(lineOut = new LineOut()); + + modulator.output.connect(carrier.modulation); + carrier.output.connect(0, lineOut.input, 0); + carrier.output.connect(0, lineOut.input, 1); + modulator.amplitude.setup(0.0, 1.0, 10.0); + carrier.amplitude.setup(0.0, 0.25, 1.0); + setupGUI(); + } + + private void setupGUI() { + setLayout(new BorderLayout()); + + add(new JLabel("Show Phase Modulation in an AudioScope"), BorderLayout.NORTH); + + // Arrange the knob in a row. + JPanel knobPanel = new JPanel(); + knobPanel.setLayout(new GridLayout(1, 0)); + + knobPanel.add(setupPortKnob(modulator.frequency, "MFreq")); + knobPanel.add(setupPortKnob(modulator.amplitude, "MAmp")); + knobPanel.add(setupPortKnob(carrier.frequency, "CFreq")); + knobPanel.add(setupPortKnob(carrier.amplitude, "CAmp")); + add(knobPanel, BorderLayout.SOUTH); + + scope = new AudioScope(synth); + scope.addProbe(carrier.output); + scope.addProbe(modulator.output); + scope.setTriggerMode(AudioScope.TriggerMode.NORMAL); + scope.getView().setControlsVisible(true); + add(scope.getView(), BorderLayout.CENTER); + scope.start(); + validate(); + } + + private RotaryTextController setupPortKnob(UnitInputPort port, String label) { + DoubleBoundedRangeModel model = PortModelFactory.createExponentialModel(port); + RotaryTextController knob = new RotaryTextController(model, 10); + knob.setBorder(BorderFactory.createTitledBorder(label)); + knob.setTitle(label); + return knob; + } + + @Override + public void start() { + // Start synthesizer using default stereo output at 44100 Hz. + synth.start(); + scope.start(); + // We only need to start the LineOut. It will pull data from the + // oscillator. + lineOut.start(); + } + + @Override + public void stop() { + scope.stop(); + synth.stop(); + } + + /* Can be run as either an application or as an applet. */ + public static void main(String[] args) { + HearSinePM applet = new HearSinePM(); + JAppletFrame frame = new JAppletFrame("Hear Phase Modulation", applet); + frame.setSize(640, 400); + frame.setVisible(true); + frame.test(); + } + +} diff --git a/examples/src/main/java/com/jsyn/examples/HearSpectralFilter.java b/examples/src/main/java/com/jsyn/examples/HearSpectralFilter.java new file mode 100644 index 0000000..cc5d8c9 --- /dev/null +++ b/examples/src/main/java/com/jsyn/examples/HearSpectralFilter.java @@ -0,0 +1,211 @@ +/* + * Copyright 2010 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.examples; + +import java.io.File; +import java.io.IOException; + +import com.jsyn.JSyn; +import com.jsyn.Synthesizer; +import com.jsyn.data.Spectrum; +import com.jsyn.unitgen.LineOut; +import com.jsyn.unitgen.PassThrough; +import com.jsyn.unitgen.SawtoothOscillatorBL; +import com.jsyn.unitgen.SineOscillator; +import com.jsyn.unitgen.SpectralFilter; +import com.jsyn.unitgen.SpectralProcessor; +import com.jsyn.unitgen.UnitOscillator; +import com.jsyn.unitgen.WhiteNoise; +import com.jsyn.util.WaveRecorder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Play a sine sweep through an FFT/IFFT pair. + * + * @author Phil Burk (C) 2010 Mobileer Inc + */ +public class HearSpectralFilter { + + private static final Logger LOGGER = LoggerFactory.getLogger(HearSpectralFilter.class); + + private Synthesizer synth; + private PassThrough center; + private UnitOscillator osc; + private UnitOscillator lfo; + private PassThrough mixer; + private SpectralFilter filter; + private LineOut lineOut; + private WaveRecorder recorder; + private final static boolean useRecorder = true; + private final static boolean useProcessor = true; + private final static int NUM_FFTS = 4; + private final static int SIZE_LOG_2 = 10; + private final static int SIZE = 1 << SIZE_LOG_2; + private SpectralProcessor[] processors; + private WhiteNoise noise; + private static int SAMPLE_RATE = 44100; + + private static class CustomSpectralProcessor extends SpectralProcessor { + public CustomSpectralProcessor() { + super(SIZE); + } + + @Override + public void processSpectrum(Spectrum inputSpectrum, Spectrum outputSpectrum) { + // pitchUpOctave( inputSpectrum, outputSpectrum ); + lowPassFilter(inputSpectrum, outputSpectrum, 1500.0); + } + + public void lowPassFilter(Spectrum inputSpectrum, Spectrum outputSpectrum, double frequency) { + inputSpectrum.copyTo(outputSpectrum); + double[] outReal = outputSpectrum.getReal(); + double[] outImag = outputSpectrum.getImaginary(); + // brickwall filter + int size = outReal.length; + int cutoff = (int) (frequency * size / SAMPLE_RATE); + int nyquist = size / 2; + for (int i = cutoff; i < nyquist; i++) { + // Bins above nyquist are mirror of ones below. + outReal[i] = outReal[size - i] = 0.0; + outImag[i] = outImag[size - i] = 0.0; + } + } + + // TODO Figure out why this sounds bad. + public void pitchUpOctave(Spectrum inputSpectrum, Spectrum outputSpectrum) { + outputSpectrum.clear(); + double[] inReal = inputSpectrum.getReal(); + double[] inImag = inputSpectrum.getImaginary(); + double[] outReal = outputSpectrum.getReal(); + double[] outImag = outputSpectrum.getImaginary(); + int size = inReal.length; + int nyquist = size / 2; + // Octave doubling by shifting the spectrum. + for (int i = nyquist - 2; i > 1; i--) { + int h = i / 2; + outReal[i] = inReal[h]; + outImag[i] = inImag[h]; + outReal[size - i] = inReal[size - h]; + outImag[size - i] = inImag[size - h]; + } + } + } + + private void test() throws IOException { + // Create a context for the synthesizer. + synth = JSyn.createSynthesizer(); + synth.setRealTime(true); + + if (useRecorder) { + File waveFile = new File("temp_recording.wav"); + // Default is stereo, 16 bits. + recorder = new WaveRecorder(synth, waveFile); + LOGGER.debug("Writing to WAV file " + waveFile.getAbsolutePath()); + } + + if (useProcessor) { + processors = new SpectralProcessor[NUM_FFTS]; + for (int i = 0; i < NUM_FFTS; i++) { + processors[i] = new CustomSpectralProcessor(); + } + } + + // Add a tone generator. + synth.add(center = new PassThrough()); + synth.add(lfo = new SineOscillator()); + synth.add(noise = new WhiteNoise()); + synth.add(mixer = new PassThrough()); + + synth.add(osc = new SawtoothOscillatorBL()); + // synth.add( osc = new SineOscillator() ); + + synth.add(filter = new SpectralFilter(NUM_FFTS, SIZE_LOG_2)); + // Add a stereo audio output unit. + synth.add(lineOut = new LineOut()); + + center.output.connect(osc.frequency); + lfo.output.connect(osc.frequency); + osc.output.connect(mixer.input); + noise.output.connect(mixer.input); + mixer.output.connect(filter.input); + if (useProcessor) { + // Pass spectra through a custom processor. + for (int i = 0; i < NUM_FFTS; i++) { + filter.getSpectralOutput(i).connect(processors[i].input); + processors[i].output.connect(filter.getSpectralInput(i)); + } + } else { + for (int i = 0; i < NUM_FFTS; i++) { + // Connect FFTs directly to IFFTs for passthrough. + filter.getSpectralOutput(i).connect(filter.getSpectralInput(i)); + } + + } + mixer.output.connect(0, lineOut.input, 0); + filter.output.connect(0, lineOut.input, 1); + + // Set the frequency and amplitude for the modulated sine wave. + center.input.set(600.0); + lfo.frequency.set(0.2); + lfo.amplitude.set(400.0); + osc.amplitude.set(0.2); + noise.amplitude.set(0.2); + + synth.start(SAMPLE_RATE); + + if (useRecorder) { + mixer.output.connect(0, recorder.getInput(), 0); + filter.output.connect(0, recorder.getInput(), 1); + // When we start the recorder it will pull data from the oscillator + // and sweeper. + recorder.start(); + } + + lineOut.start(); + + LOGGER.debug("You should now be hearing a clean oscillator on the left channel,"); + LOGGER.debug("and the FFT->IFFT processed signal on the right channel."); + + // Sleep while the sound is generated in the background. + try { + double time = synth.getCurrentTime(); + // Sleep for a few seconds. + synth.sleepUntil(time + 10.0); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + if (recorder != null) { + recorder.stop(); + recorder.close(); + } + + LOGGER.debug("Stop playing. -------------------"); + // Stop everything. + synth.stop(); + } + + public static void main(String[] args) { + try { + new HearSpectralFilter().test(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } +} diff --git a/examples/src/main/java/com/jsyn/examples/ListAudioDevices.java b/examples/src/main/java/com/jsyn/examples/ListAudioDevices.java new file mode 100644 index 0000000..097ffdc --- /dev/null +++ b/examples/src/main/java/com/jsyn/examples/ListAudioDevices.java @@ -0,0 +1,50 @@ +/* + * 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.examples; + +import com.jsyn.devices.AudioDeviceFactory; +import com.jsyn.devices.AudioDeviceManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ListAudioDevices { + + private static final Logger LOGGER = LoggerFactory.getLogger(ListAudioDevices.class); + + /** + * @param args + */ + public static void main(String[] args) { + AudioDeviceManager audioManager = AudioDeviceFactory.createAudioDeviceManager(); + + int numDevices = audioManager.getDeviceCount(); + for (int i = 0; i < numDevices; i++) { + String deviceName = audioManager.getDeviceName(i); + int maxInputs = audioManager.getMaxInputChannels(i); + int maxOutputs = audioManager.getMaxInputChannels(i); + boolean isDefaultInput = (i == audioManager.getDefaultInputDeviceID()); + boolean isDefaultOutput = (i == audioManager.getDefaultOutputDeviceID()); + LOGGER.debug("#" + i + " : " + deviceName); + LOGGER.debug(" max inputs : " + maxInputs + + (isDefaultInput ? " (default)" : "")); + LOGGER.debug(" max outputs: " + maxOutputs + + (isDefaultOutput ? " (default)" : "")); + } + + } + +} diff --git a/examples/src/main/java/com/jsyn/examples/LongEcho.java b/examples/src/main/java/com/jsyn/examples/LongEcho.java new file mode 100644 index 0000000..16e71f3 --- /dev/null +++ b/examples/src/main/java/com/jsyn/examples/LongEcho.java @@ -0,0 +1,128 @@ +/* + * Copyright 2010 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.examples; + +import java.io.File; +import java.io.IOException; + +import com.jsyn.JSyn; +import com.jsyn.Synthesizer; +import com.jsyn.data.FloatSample; +import com.jsyn.devices.AudioDeviceManager; +import com.jsyn.unitgen.ChannelIn; +import com.jsyn.unitgen.ChannelOut; +import com.jsyn.unitgen.FixedRateMonoReader; +import com.jsyn.unitgen.FixedRateMonoWriter; +import com.jsyn.unitgen.Maximum; +import com.jsyn.unitgen.Minimum; +import com.jsyn.util.WaveFileWriter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Echo the input using a circular buffer in a sample. + * + * @author Phil Burk (C) 2010 Mobileer Inc + */ +public class LongEcho { + + private static final Logger LOGGER = LoggerFactory.getLogger(LongEcho.class); + + final static int DELAY_SECONDS = 4; + Synthesizer synth; + ChannelIn channelIn; + ChannelOut channelOut; + FloatSample sample; + FixedRateMonoReader reader; + FixedRateMonoWriter writer; + Minimum minner; + Maximum maxxer; + + private void test() throws IOException { + // Create a context for the synthesizer. + synth = JSyn.createSynthesizer(); + synth.add(channelIn = new ChannelIn()); + synth.add(channelOut = new ChannelOut()); + + synth.add(minner = new Minimum()); + synth.add(maxxer = new Maximum()); + synth.add(reader = new FixedRateMonoReader()); + synth.add(writer = new FixedRateMonoWriter()); + + sample = new FloatSample(44100 * DELAY_SECONDS, 1); + + maxxer.inputB.set(-0.98); // clip + minner.inputB.set(0.98); + + // Connect the input to the output. + channelIn.output.connect(minner.inputA); + minner.output.connect(maxxer.inputA); + maxxer.output.connect(writer.input); + + reader.output.connect(channelOut.input); + + // Both stereo. + int numInputChannels = 2; + int numOutputChannels = 2; + synth.start(44100, AudioDeviceManager.USE_DEFAULT_DEVICE, numInputChannels, + AudioDeviceManager.USE_DEFAULT_DEVICE, numOutputChannels); + + writer.start(); + channelOut.start(); + + // For a long echo, read cursor should be just in front of the write cursor. + reader.dataQueue.queue(sample, 1000, sample.getNumFrames() - 1000); + // Loop both forever. + reader.dataQueue.queueLoop(sample, 0, sample.getNumFrames()); + writer.dataQueue.queueLoop(sample, 0, sample.getNumFrames()); + LOGGER.debug("Start talking. You should hear an echo after " + DELAY_SECONDS + + " seconds."); + // Sleep a while. + try { + double time = synth.getCurrentTime(); + // Sleep for a while + synth.sleepUntil(time + 30.0); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + saveEcho(new File("saved_echo.wav")); + // Stop everything. + synth.stop(); + } + + private void saveEcho(File file) throws IOException { + WaveFileWriter writer = new WaveFileWriter(file); + writer.setFrameRate(44100); + writer.setSamplesPerFrame(1); + writer.setBitsPerSample(16); + float[] buffer = new float[sample.getNumFrames()]; + sample.read(buffer); + for (float v : buffer) { + writer.write(v); + } + writer.close(); + } + + public static void main(String[] args) { + try { + new LongEcho().test(); + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/examples/src/main/java/com/jsyn/examples/MonoPassThrough.java b/examples/src/main/java/com/jsyn/examples/MonoPassThrough.java new file mode 100644 index 0000000..0e81abf --- /dev/null +++ b/examples/src/main/java/com/jsyn/examples/MonoPassThrough.java @@ -0,0 +1,66 @@ +/* + * Copyright 2010 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.examples; + +import com.jsyn.JSyn; +import com.jsyn.Synthesizer; +import com.jsyn.devices.AudioDeviceManager; +import com.jsyn.unitgen.ChannelIn; +import com.jsyn.unitgen.ChannelOut; + +/** + * Pass audio input to audio output. + * + * @author Phil Burk (C) 2010 Mobileer Inc + */ +public class MonoPassThrough { + Synthesizer synth; + ChannelIn channelIn; + ChannelOut channelOut; + + private void test() { + // Create a context for the synthesizer. + synth = JSyn.createSynthesizer(); + synth.add(channelIn = new ChannelIn()); + synth.add(channelOut = new ChannelOut()); + // Connect the input to the output. + channelIn.output.connect(channelOut.input); + + // Both stereo. + int numInputChannels = 2; + int numOutputChannels = 2; + synth.start(48000, AudioDeviceManager.USE_DEFAULT_DEVICE, numInputChannels, + AudioDeviceManager.USE_DEFAULT_DEVICE, numOutputChannels); + + // We only need to start the ChannelOut. It will pull data from the ChannelIn. + channelOut.start(); + // Sleep a while. + try { + double time = synth.getCurrentTime(); + // Sleep for a few seconds. + synth.sleepUntil(time + 4.0); + } catch (InterruptedException e) { + e.printStackTrace(); + } + // Stop everything. + synth.stop(); + } + + public static void main(String[] args) { + new MonoPassThrough().test(); + } +} diff --git a/examples/src/main/java/com/jsyn/examples/NotesToTone.java b/examples/src/main/java/com/jsyn/examples/NotesToTone.java new file mode 100644 index 0000000..a1b06fa --- /dev/null +++ b/examples/src/main/java/com/jsyn/examples/NotesToTone.java @@ -0,0 +1,219 @@ +/* + * Copyright 2010 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. + */ +/** + * If you play notes fast enough they become a tone. + * + * Play a sine wave modulated by an envelope. + * Speed up the envelope until it is playing at audio rate. + * Slow down the oscillator until it becomes an LFO amp modulator. + * Use a LatchZeroCrossing to stop at the end of a sine wave cycle when we are finished. + * + * @author Phil Burk, (C) 2010 Mobileer Inc + */ + +package com.jsyn.examples; + +import java.io.File; +import java.io.IOException; + +import com.jsyn.JSyn; +import com.jsyn.Synthesizer; +import com.jsyn.data.SegmentedEnvelope; +import com.jsyn.unitgen.ExponentialRamp; +import com.jsyn.unitgen.LatchZeroCrossing; +import com.jsyn.unitgen.LineOut; +import com.jsyn.unitgen.SineOscillator; +import com.jsyn.unitgen.UnitOscillator; +import com.jsyn.unitgen.VariableRateDataReader; +import com.jsyn.unitgen.VariableRateMonoReader; +import com.jsyn.util.WaveRecorder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * When notes speed up they can become a new tone.
+ * Multiply an oscillator and an envelope. Speed up the envelope until it becomes a tone. Slow down + * the oscillator until it acts like an envelope. Write the resulting audio to a WAV file. + * + * @author Phil Burk (C) 2011 Mobileer Inc + */ + +public class NotesToTone { + + private static final Logger LOGGER = LoggerFactory.getLogger(NotesToTone.class); + + private final static double SONG_AMPLITUDE = 0.7; + private final static double INTRO_DURATION = 2.0; + private final static double OUTRO_DURATION = 2.0; + private final static double RAMP_DURATION = 20.0; + private final static double LOW_FREQUENCY = 1.0; + private final static double HIGH_FREQUENCY = 800.0; + + private final static boolean useRecorder = true; + private WaveRecorder recorder; + + private Synthesizer synth; + private ExponentialRamp envSweeper; + private ExponentialRamp oscSweeper; + private VariableRateDataReader envelopePlayer; + private UnitOscillator osc; + private LatchZeroCrossing latch; + private LineOut lineOut; + private SegmentedEnvelope envelope; + + private void play() throws IOException { + synth = JSyn.createSynthesizer(); + synth.setRealTime(true); + + if (useRecorder) { + File waveFile = new File("notes_to_tone.wav"); + // Default is stereo, 16 bits. + recorder = new WaveRecorder(synth, waveFile, 1); + LOGGER.debug("Writing to WAV file " + waveFile.getAbsolutePath()); + } + + createUnits(); + + connectUnits(); + + setupEnvelope(); + + osc.amplitude.set(SONG_AMPLITUDE); + + // Ramp the rate of the envelope up until it becomes an audible tone. + envSweeper.current.set(LOW_FREQUENCY); + envSweeper.input.set(LOW_FREQUENCY); + envSweeper.time.set(RAMP_DURATION); + + // Ramp the rate of the oscillator down until it becomes an LFO. + oscSweeper.current.set(HIGH_FREQUENCY); + oscSweeper.input.set(HIGH_FREQUENCY); + oscSweeper.time.set(RAMP_DURATION); + + // Start synthesizer using default stereo output at 44100 Hz. + synth.start(); + + // When we start the recorder it will pull data from the oscillator and + // sweeper. + if (recorder != null) { + recorder.start(); + } + + // We also need to start the LineOut if we want to hear it now. + lineOut.start(); + + // Get synthesizer time in seconds. + double timeNow = synth.getCurrentTime(); + + // Schedule start of ramps. + double songDuration = INTRO_DURATION + RAMP_DURATION + OUTRO_DURATION; + envSweeper.input.set(HIGH_FREQUENCY, timeNow + INTRO_DURATION); + oscSweeper.input.set(LOW_FREQUENCY, timeNow + INTRO_DURATION); + + // Arm zero crossing latch + latch.gate.set(0.0, timeNow + songDuration); + + // Sleep while the sound is being generated in the background thread. + try { + synth.sleepUntil(timeNow + songDuration + 2.0); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + if (recorder != null) { + recorder.stop(); + recorder.close(); + } + // Stop everything. + synth.stop(); + } + + private void createUnits() { + // Add a tone generators. + synth.add(osc = new SineOscillator()); + // Add a controller that will sweep the envelope rate up. + synth.add(envSweeper = new ExponentialRamp()); + // Add a controller that will sweep the oscillator down. + synth.add(oscSweeper = new ExponentialRamp()); + + synth.add(latch = new LatchZeroCrossing()); + // Add an output unit. + synth.add(lineOut = new LineOut()); + + // Add an envelope player. + synth.add(envelopePlayer = new VariableRateMonoReader()); + } + + private void connectUnits() { + oscSweeper.output.connect(osc.frequency); + osc.output.connect(latch.input); + // Latch when sine LFO crosses zero. + latch.output.connect(envelopePlayer.amplitude); + + envSweeper.output.connect(envelopePlayer.rate); + + // Connect the envelope player to the audio output. + envelopePlayer.output.connect(0, lineOut.input, 0); + // crossFade.output.connect( 0, lineOut.input, 1 ); + + if (recorder != null) { + envelopePlayer.output.connect(0, recorder.getInput(), 0); + // crossFade.output.connect( 0, recorder.getInput(), 1 ); + } + } + + private void setupEnvelope() { + // Setup envelope. The envelope has a total duration of 1.0 seconds. + // Values are (duration,target) pairs. + double[] pairs = new double[5 * 2 * 2]; + int i = 0; + // duration, target for delay + pairs[i++] = 0.15; + pairs[i++] = 0.0; + // duration, target for attack + pairs[i++] = 0.05; + pairs[i++] = 1.0; + // duration, target for release + pairs[i++] = 0.1; + pairs[i++] = 0.6; + // duration, target for sustain + pairs[i++] = 0.1; + pairs[i++] = 0.6; + // duration, target for release + pairs[i++] = 0.1; + pairs[i++] = 0.0; + // Create mirror image of this envelope. + int halfLength = i; + while (i < pairs.length) { + pairs[i] = pairs[i - halfLength]; + i++; + pairs[i] = pairs[i - halfLength] * -1.0; + i++; + } + envelope = new SegmentedEnvelope(pairs); + + envelopePlayer.dataQueue.queueLoop(envelope, 0, envelope.getNumFrames()); + } + + public static void main(String[] args) { + try { + new NotesToTone().play(); + } catch (IOException e) { + e.printStackTrace(); + } + } + +} diff --git a/examples/src/main/java/com/jsyn/examples/PlayChords.java b/examples/src/main/java/com/jsyn/examples/PlayChords.java new file mode 100644 index 0000000..9bd9245 --- /dev/null +++ b/examples/src/main/java/com/jsyn/examples/PlayChords.java @@ -0,0 +1,178 @@ +/* + * 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.examples; + +import com.jsyn.JSyn; +import com.jsyn.Synthesizer; +import com.jsyn.instruments.SubtractiveSynthVoice; +import com.jsyn.unitgen.LineOut; +import com.jsyn.unitgen.UnitVoice; +import com.jsyn.util.VoiceAllocator; +import com.softsynth.math.AudioMath; +import com.softsynth.shared.time.TimeStamp; + +/** + * Play chords and melody using the VoiceAllocator. + * + * @author Phil Burk (C) 2009 Mobileer Inc + */ +public class PlayChords { + private static final int MAX_VOICES = 8; + private Synthesizer synth; + private VoiceAllocator allocator; + private LineOut lineOut; + /** Number of seconds to generate music in advance of presentation-time. */ + private double advance = 0.2; + private double secondsPerBeat = 0.6; + // on time over note duration + private double dutyCycle = 0.8; + private double measure = secondsPerBeat * 4.0; + private UnitVoice[] voices; + + private void test() { + synth = JSyn.createSynthesizer(); + + // Add an output. + synth.add(lineOut = new LineOut()); + + voices = new UnitVoice[MAX_VOICES]; + for (int i = 0; i < MAX_VOICES; i++) { + SubtractiveSynthVoice voice = new SubtractiveSynthVoice(); + synth.add(voice); + voice.getOutput().connect(0, lineOut.input, 0); + voice.getOutput().connect(0, lineOut.input, 1); + voices[i] = voice; + } + allocator = new VoiceAllocator(voices); + + // Start synthesizer using default stereo output at 44100 Hz. + synth.start(); + // We only need to start the LineOut. It will pull data from the + // voices. + lineOut.start(); + + // Get synthesizer time in seconds. + double timeNow = synth.getCurrentTime(); + + // Advance to a near future time so we have a clean start. + double time = timeNow + 1.0; + + try { + int tonic = 60 - 12; + for (int i = 0; i < 4; i++) { + playMajorMeasure1(time, tonic); + time += measure; + catchUp(time); + playMajorMeasure1(time, tonic + 4); + time += measure; + catchUp(time); + playMajorMeasure1(time, tonic + 7); + time += measure; + catchUp(time); + playMinorMeasure1(time, tonic + 2); // minor chord + time += measure; + catchUp(time); + } + time += secondsPerBeat; + catchUp(time); + + } catch (InterruptedException e) { + e.printStackTrace(); + } + + // Stop everything. + synth.stop(); + } + + private void playMinorMeasure1(double time, int base) throws InterruptedException { + int p2 = base + 3; + int p3 = base + 7; + playChord1(time, base, p2, p3); + playNoodle1(time, base + 24, p2 + 24, p3 + 24); + } + + private void playMajorMeasure1(double time, int base) throws InterruptedException { + int p2 = base + 4; + int p3 = base + 7; + playChord1(time, base, p2, p3); + playNoodle1(time, base + 24, p2 + 24, p3 + 24); + } + + private void playNoodle1(double time, int p1, int p2, int p3) { + double secondsPerNote = secondsPerBeat * 0.5; + for (int i = 0; i < 8; i++) { + int p = pickFromThree(p1, p2, p3); + noteOn(time, p); + noteOff(time + dutyCycle * secondsPerNote, p); + time += secondsPerNote; + } + } + + private int pickFromThree(int p1, int p2, int p3) { + int r = (int) (Math.random() * 3.0); + if (r < 1) + return p1; + else if (r < 2) + return p2; + else + return p3; + } + + private void playChord1(double time, int p1, int p2, int p3) throws InterruptedException { + double dur = dutyCycle * secondsPerBeat; + playTriad(time, dur, p1, p2, p3); + time += secondsPerBeat; + playTriad(time, dur, p1, p2, p3); + time += secondsPerBeat; + playTriad(time, dur * 0.25, p1, p2, p3); + time += secondsPerBeat * 0.25; + playTriad(time, dur * 0.25, p1, p2, p3); + time += secondsPerBeat * 0.75; + playTriad(time, dur, p1, p2, p3); + time += secondsPerBeat; + } + + private void playTriad(double time, double dur, int p1, int p2, int p3) + throws InterruptedException { + noteOn(time, p1); + noteOn(time, p2); + noteOn(time, p3); + double offTime = time + dur; + noteOff(offTime, p1); + noteOff(offTime, p2); + noteOff(offTime, p3); + } + + private void catchUp(double time) throws InterruptedException { + synth.sleepUntil(time - advance); + } + + private void noteOff(double time, int noteNumber) { + allocator.noteOff(noteNumber, new TimeStamp(time)); + } + + private void noteOn(double time, int noteNumber) { + double frequency = AudioMath.pitchToFrequency(noteNumber); + double amplitude = 0.2; + TimeStamp timeStamp = new TimeStamp(time); + allocator.noteOn(noteNumber, frequency, amplitude, timeStamp); + } + + public static void main(String[] args) { + new PlayChords().test(); + } +} diff --git a/examples/src/main/java/com/jsyn/examples/PlayCustomUnit.java b/examples/src/main/java/com/jsyn/examples/PlayCustomUnit.java new file mode 100644 index 0000000..609c351 --- /dev/null +++ b/examples/src/main/java/com/jsyn/examples/PlayCustomUnit.java @@ -0,0 +1,73 @@ +/* + * Copyright 2010 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.examples; + +import com.jsyn.JSyn; +import com.jsyn.Synthesizer; +import com.jsyn.unitgen.LineOut; +import com.jsyn.unitgen.SineOscillator; +import com.jsyn.unitgen.UnitOscillator; + +/** + * Play a tone using a JSyn oscillator and process it using a custom unit generator. + * + * @author Phil Burk (C) 2010 Mobileer Inc + */ +public class PlayCustomUnit { + private Synthesizer synth; + private UnitOscillator osc; + private CustomCubeUnit cuber; + private LineOut lineOut; + + private void test() { + synth = JSyn.createSynthesizer(); + // Add a tone generator. + synth.add(osc = new SineOscillator()); + // Add a tone generator. + synth.add(cuber = new CustomCubeUnit()); + // Add an output to the DAC. + synth.add(lineOut = new LineOut()); + // Connect the oscillator to the cuber. + osc.output.connect(0, cuber.input, 0); + // Connect the cuber to the right output. + cuber.output.connect(0, lineOut.input, 1); + // Send the original to the left output for comparison. + osc.output.connect(0, lineOut.input, 0); + + osc.frequency.set(240.0); + + // Start synthesizer using default stereo output at 44100 Hz. + synth.start(); + // We only need to start the LineOut. + // It will pull data from the cuber and the oscillator. + lineOut.start(); + // Sleep while the sound is generated in the background. + try { + double time = synth.getCurrentTime(); + // Sleep for a few seconds. + synth.sleepUntil(time + 10.0); + } catch (InterruptedException e) { + e.printStackTrace(); + } + // Stop everything. + synth.stop(); + } + + public static void main(String[] args) { + new PlayCustomUnit().test(); + } +} diff --git a/examples/src/main/java/com/jsyn/examples/PlayFunction.java b/examples/src/main/java/com/jsyn/examples/PlayFunction.java new file mode 100644 index 0000000..9c389c9 --- /dev/null +++ b/examples/src/main/java/com/jsyn/examples/PlayFunction.java @@ -0,0 +1,90 @@ +/* + * Copyright 2010 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.examples; + +import com.jsyn.JSyn; +import com.jsyn.data.Function; +import com.jsyn.unitgen.FunctionOscillator; +import com.jsyn.unitgen.LineOut; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Play a tone using a FunctionOscillator. + * + * @author Phil Burk (C) 2010 Mobileer Inc + */ +public class PlayFunction { + + private static final Logger LOGGER = LoggerFactory.getLogger(PlayFunction.class); + + private void test() { + // Create a context for the synthesizer. + var synth = JSyn.createSynthesizer(); + + // Start synthesizer using default stereo output at 44100 Hz. + synth.start(); + + // Add a FunctionOscillator + var oscillator = new FunctionOscillator(); + synth.add(oscillator); + + // Define a function that gives the shape of the waveform. + Function func = input -> { + // Input ranges from -1.0 to 1.0 + double s = Math.sin(input * Math.PI * 2.0); + return s * s * s; + }; + + oscillator.function.set(func); + + // Add a stereo audio output unit. + var lineOut = new LineOut(); + synth.add(lineOut); + + // Connect the oscillator to both channels of the output. + oscillator.output.connect(0, lineOut.input, 0); + oscillator.output.connect(0, lineOut.input, 1); + + // Set the frequency and amplitude for the sine wave. + oscillator.frequency.set(345.0); + oscillator.amplitude.set(0.6); + + // We only need to start the LineOut. It will pull data from the + // oscillator. + lineOut.start(); + + LOGGER.debug("You should now be hearing a sine wave. ---------"); + + // Sleep while the sound is generated in the background. + try { + double time = synth.getCurrentTime(); + // Sleep for a few seconds. + synth.sleepUntil(time + 4.0); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + LOGGER.debug("Stop playing. -------------------"); + // Stop everything. + synth.stop(); + } + + public static void main(String[] args) { + new PlayFunction().test(); + } +} diff --git a/examples/src/main/java/com/jsyn/examples/PlayGrains.java b/examples/src/main/java/com/jsyn/examples/PlayGrains.java new file mode 100644 index 0000000..82b390e --- /dev/null +++ b/examples/src/main/java/com/jsyn/examples/PlayGrains.java @@ -0,0 +1,211 @@ +/* + * Copyright 2011 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.examples; + +import java.awt.BorderLayout; +import java.awt.GridLayout; +import java.io.File; +import java.io.IOException; + +import javax.swing.BorderFactory; +import javax.swing.JApplet; +import javax.swing.JLabel; +import javax.swing.JPanel; + +import com.jsyn.JSyn; +import com.jsyn.Synthesizer; +import com.jsyn.data.FloatSample; +import com.jsyn.ports.UnitInputPort; +import com.jsyn.scope.AudioScope; +import com.jsyn.swing.DoubleBoundedRangeModel; +import com.jsyn.swing.JAppletFrame; +import com.jsyn.swing.PortModelFactory; +import com.jsyn.swing.RotaryTextController; +import com.jsyn.unitgen.ContinuousRamp; +import com.jsyn.unitgen.GrainFarm; +import com.jsyn.unitgen.LineOut; +import com.jsyn.unitgen.SampleGrainFarm; +import com.jsyn.util.SampleLoader; +import com.jsyn.util.WaveRecorder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Play with Granular Synthesis tools. + * + * @author Phil Burk (C) 2011 Mobileer Inc + */ +public class PlayGrains extends JApplet { + + private static final Logger LOGGER = LoggerFactory.getLogger(PlayGrains.class); + + private Synthesizer synth; + private LineOut lineOut; + private AudioScope scope; + private GrainFarm grainFarm; + private ContinuousRamp ramp; + private static final int NUM_GRAINS = 32; + private FloatSample sample; + private WaveRecorder recorder; + private final static boolean useRecorder = false; + + private static final boolean useSample = true; + // If you enable useSample then you will need to replace the file name below with a valid + // file name on your computer. + private static final File sampleFile = new File("notes_to_tone.wav"); + + /* Can be run as either an application or as an applet. */ + public static void main(String[] args) { + PlayGrains applet = new PlayGrains(); + JAppletFrame frame = new JAppletFrame("PlayGrains", applet); + frame.setSize(840, 500); + frame.setVisible(true); + frame.test(); + } + + private void setupGUI() { + setLayout(new BorderLayout()); + + add(BorderLayout.NORTH, + new JLabel("PlayGrains in an AudioScope - JSyn V" + synth.getVersion())); + + scope = new AudioScope(synth); + + // scope.addProbe( osc.output ); + scope.addProbe(grainFarm.output); + + scope.setTriggerMode(AudioScope.TriggerMode.NORMAL); + scope.getView().setControlsVisible(true); + add(BorderLayout.CENTER, scope.getView()); + scope.start(); + + // Arrange the knob in a row. + JPanel knobPanel = new JPanel(); + knobPanel.setLayout(new GridLayout(1, 0)); + + if (useSample) { + SampleGrainFarm sampleGrainFarm = (SampleGrainFarm) grainFarm; + knobPanel.add(setupLinearPortKnob(ramp.time, 0.001, 10.0, "Time")); + knobPanel.add(setupLinearPortKnob(ramp.input, -1.0, 1.0, "Position")); + knobPanel.add(setupLinearPortKnob(sampleGrainFarm.positionRange, 0.0, 0.5, "PosRange")); + } + knobPanel.add(setupPortKnob(grainFarm.density, 1.0, "Density")); + knobPanel.add(setupPortKnob(grainFarm.rate, 4.0, "Rate")); + knobPanel.add(setupPortKnob(grainFarm.rateRange, 3.0, "RateRange")); + knobPanel.add(setupPortKnob(grainFarm.duration, 0.1, "Duration")); + knobPanel.add(setupPortKnob(grainFarm.durationRange, 3.0, "DurRange")); + knobPanel.add(setupPortKnob(grainFarm.amplitude, 6.0, "Amplitude")); + knobPanel.add(setupPortKnob(grainFarm.amplitudeRange, 1.0, "AmpRange")); + add(knobPanel, BorderLayout.SOUTH); + + validate(); + } + + private RotaryTextController setupLinearPortKnob(UnitInputPort port, double min, double max, + String label) { + port.setMinimum(min); + port.setMaximum(max); + + DoubleBoundedRangeModel model = PortModelFactory.createLinearModel(port); + RotaryTextController knob = new RotaryTextController(model, 10); + knob.setBorder(BorderFactory.createTitledBorder(label)); + knob.setTitle(label); + return knob; + } + + private RotaryTextController setupPortKnob(UnitInputPort port, double max, String label) { + port.setMinimum(0.0); + port.setMaximum(max); + + DoubleBoundedRangeModel model = PortModelFactory.createExponentialModel(port); + RotaryTextController knob = new RotaryTextController(model, 10); + knob.setBorder(BorderFactory.createTitledBorder(label)); + knob.setTitle(label); + return knob; + } + + @Override + public void start() { + synth = JSyn.createSynthesizer(); + + try { + + if (useRecorder) { + File waveFile = new File("temp_recording.wav"); + // Record mono 16 bits. + recorder = new WaveRecorder(synth, waveFile, 1); + LOGGER.debug("Writing to WAV file " + waveFile.getAbsolutePath()); + } + + if (useSample) { + sample = SampleLoader.loadFloatSample(sampleFile); + SampleGrainFarm sampleGrainFarm = new SampleGrainFarm(); + synth.add(ramp = new ContinuousRamp()); + sampleGrainFarm.setSample(sample); + ramp.output.connect(sampleGrainFarm.position); + grainFarm = sampleGrainFarm; + } else { + grainFarm = new GrainFarm(); + } + + synth.add(grainFarm); + + grainFarm.allocate(NUM_GRAINS); + + // Add an output so we can hear the grains. + synth.add(lineOut = new LineOut()); + + grainFarm.getOutput().connect(0, lineOut.input, 0); + grainFarm.getOutput().connect(0, lineOut.input, 1); + + // Start synthesizer using default stereo output at 44100 Hz. + synth.start(); + + if (useRecorder) { + grainFarm.output.connect(0, recorder.getInput(), 0); + // When we start the recorder it will pull data from the + // oscillator + // and sweeper. + recorder.start(); + } + + setupGUI(); + // We only need to start the LineOut. It will pull data from the + // oscillator. + lineOut.start(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + } + + @Override + public void stop() { + try { + if (recorder != null) { + recorder.stop(); + recorder.close(); + } + } catch (IOException e) { + e.printStackTrace(); + } + scope.stop(); + synth.stop(); + } + +} diff --git a/examples/src/main/java/com/jsyn/examples/PlayMIDI.java b/examples/src/main/java/com/jsyn/examples/PlayMIDI.java new file mode 100644 index 0000000..92add86 --- /dev/null +++ b/examples/src/main/java/com/jsyn/examples/PlayMIDI.java @@ -0,0 +1,233 @@ +/* + * Copyright 2010 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.examples; + +import java.io.IOException; + +import com.jsyn.JSyn; +import com.jsyn.Synthesizer; +import com.jsyn.instruments.DualOscillatorSynthVoice; +import com.jsyn.midi.MidiConstants; +import com.jsyn.midi.MidiSynthesizer; +import com.jsyn.unitgen.LineOut; +import com.jsyn.util.MultiChannelSynthesizer; +import com.jsyn.util.VoiceDescription; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Send MIDI messages to JSyn based MIDI synthesizer. + * + * @author Phil Burk (C) 2010 Mobileer Inc + */ +public class PlayMIDI { + + private static final Logger LOGGER = LoggerFactory.getLogger(PlayMIDI.class); + + private static final int NUM_CHANNELS = 16; + private static final int VOICES_PER_CHANNEL = 6; + private Synthesizer synth; + private MidiSynthesizer midiSynthesizer; + private LineOut lineOut; + + private VoiceDescription voiceDescription; + private MultiChannelSynthesizer multiSynth; + + public static void main(String[] args) { + PlayMIDI app = new PlayMIDI(); + try { + VoiceDescription description = DualOscillatorSynthVoice.getVoiceDescription(); + app.test(description); + LOGGER.debug("Test complete"); + } catch (IOException | InterruptedException e) { + e.printStackTrace(); + } + System.exit(0); + } + + public void sendMidiMessage(byte[] bytes) { + midiSynthesizer.onReceive(bytes, 0, bytes.length); + } + + public void sendNoteOff(int channel, int pitch, int velocity) { + midiCommand(MidiConstants.NOTE_OFF + channel, pitch, velocity); + } + + public void sendNoteOn(int channel, int pitch, int velocity) { + midiCommand(MidiConstants.NOTE_ON + channel, pitch, velocity); + } + + public void sendControlChange(int channel, int index, int value) { + midiCommand(MidiConstants.CONTROL_CHANGE + channel, index, value); + } + + /** + * @param channel + * @param program starts at zero + */ + private void sendProgramChange(int channel, int program) { + midiCommand(MidiConstants.PROGRAM_CHANGE + channel, program); + + } + + /** + * Send either RPN or NRPN. + */ + public void sendParameter(int channel, int index14, int value14, int controllerXPN) { + int indexLsb = index14 & 0x07F; + int indexMsb = (index14 >> 7) & 0x07F; + int valueLsb = value14 & 0x07F; + int valueMsb = (value14 >> 7) & 0x07F; + sendControlChange(channel, controllerXPN + 1, indexMsb); + sendControlChange(channel, controllerXPN, indexLsb); + sendControlChange(channel, MidiConstants.CONTROLLER_DATA_ENTRY, valueMsb); + sendControlChange(channel, MidiConstants.CONTROLLER_DATA_ENTRY_LSB, valueLsb); + sendControlChange(channel, controllerXPN + 1, 0x7F); // NULL RPN index + sendControlChange(channel, controllerXPN, 0x7F); // to deactivate RPN + } + + public void sendRPN(int channel, int index14, int value14) { + sendParameter(channel, index14, value14, MidiConstants.CONTROLLER_RPN_LSB); + } + + public void sendNRPN(int channel, int index14, int value14) { + sendParameter(channel, index14, value14, MidiConstants.CONTROLLER_NRPN_LSB); + } + + private void midiCommand(int status, int data1, int data2) { + byte[] buffer = new byte[3]; + buffer[0] = (byte) status; + buffer[1] = (byte) data1; + buffer[2] = (byte) data2; + sendMidiMessage(buffer); + } + + private void midiCommand(int status, int data1) { + byte[] buffer = new byte[2]; + buffer[0] = (byte) status; + buffer[1] = (byte) data1; + sendMidiMessage(buffer); + } + + public int test(VoiceDescription description) throws IOException, InterruptedException { + setupSynth(description); + + //playOctaveUsingBend(); + playSameNotesBent(); + + // Setup all the channels. + int maxChannels = 8; + for (int channel = 0; channel < maxChannels; channel++) { + sendProgramChange(channel, channel); + } + playNotePerChannel(maxChannels); + + return 0; + } + + private void playOctaveUsingBend() throws InterruptedException { + sendProgramChange(0, 0); + float range0 = 12.0f; + sendPitchBendRange(0, range0); + for(int i = 0; i < 13; i++) { + LOGGER.debug("Bend to pitch " + i); + sendPitchBend(0, i / range0); + sendNoteOn(0, 60, 100); + synth.sleepFor(0.5); + sendNoteOff(0, 60, 100); + synth.sleepFor(0.5); + } + } + + private void playSameNotesBent() throws InterruptedException { + sendProgramChange(0, 0); + sendProgramChange(1, 0); + float range0 = 2.3f; + float range1 = 6.8f; + sendPitchBendRange(0, range0); + sendPitchBendRange(1, range1); + sendPitchBend(0, 0.0f / range0); // bend by 0 semitones + sendPitchBend(1, 1.0f / range1); // bend by 1 semitones + + LOGGER.debug("These two notes should play at the same pitch."); + sendNoteOn(0, 61, 100); + synth.sleepFor(0.5); + sendNoteOff(0, 61, 100); + + sendNoteOn(1, 60, 100); + synth.sleepFor(0.5); + sendNoteOff(1, 60, 100); + + synth.sleepFor(2.0); + LOGGER.debug("------ done ---------------"); + } + + /** + * + * @param channel + * @param normalizedBend between -1 and +1 + */ + private void sendPitchBend(int channel, float normalizedBend) { + final int BEND_MIN = 0x0000; + final int BEND_CENTER = 0x2000; + final int BEND_MAX = 0x3FFF; + int bend = BEND_CENTER + (int)(BEND_CENTER * normalizedBend); + if (bend < BEND_MIN) bend = BEND_MIN; + else if (bend > BEND_MAX) bend = BEND_MAX; + int lsb = bend & 0x07F; + int msb = (bend >> 7) & 0x07F; + midiCommand(MidiConstants.PITCH_BEND + channel, lsb, msb); + } + + private void sendPitchBendRange(int channel, float range0) { + int semitones = (int)range0; + int cents = (int) (100 * (range0 - semitones)); + int value = (semitones << 7) + cents; + sendRPN(channel, MidiConstants.RPN_BEND_RANGE, value); + } + + private void playNotePerChannel(int maxChannels) throws InterruptedException { + // Play notes on those channels. + for (int channel = 0; channel < maxChannels; channel++) { + sendNoteOn(channel, 60 + channel, 100); + synth.sleepFor(0.5); + sendNoteOff(channel, 60 + channel, 100); + synth.sleepFor(0.5); + } + } + + private void setupSynth(VoiceDescription description) { + synth = JSyn.createSynthesizer(); + + // Add an output. + synth.add(lineOut = new LineOut()); + + voiceDescription = description; + multiSynth = new MultiChannelSynthesizer(); + final int startChannel = 0; + multiSynth.setup(synth, startChannel, NUM_CHANNELS, VOICES_PER_CHANNEL, voiceDescription); + midiSynthesizer = new MidiSynthesizer(multiSynth); + + multiSynth.getOutput().connect(0,lineOut.input, 0); + multiSynth.getOutput().connect(1,lineOut.input, 1); + + // Start synthesizer using default stereo output at 44100 Hz. + synth.start(); + lineOut.start(); + } + +} diff --git a/examples/src/main/java/com/jsyn/examples/PlayNotes.java b/examples/src/main/java/com/jsyn/examples/PlayNotes.java new file mode 100644 index 0000000..636dc1b --- /dev/null +++ b/examples/src/main/java/com/jsyn/examples/PlayNotes.java @@ -0,0 +1,102 @@ +/* + * 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.examples; + +import com.jsyn.JSyn; +import com.jsyn.unitgen.LineOut; +import com.jsyn.unitgen.SawtoothOscillator; +import com.softsynth.shared.time.TimeStamp; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Play notes using timestamped noteOn and noteOff methods of the UnitVoice. + * + * @author Phil Burk (C) 2009 Mobileer Inc + */ +public class PlayNotes { + + private static final Logger LOGGER = LoggerFactory.getLogger(PlayNotes.class); + + private void test() { + + // Create a context for the synthesizer. + var synth = JSyn.createSynthesizer(); + // Set output latency to 123 msec because this is not an interactive app. + synth.getAudioDeviceManager().setSuggestedOutputLatency(0.123); + + // Add a tone generator. + var voice = new SawtoothOscillator(); + synth.add(voice); + // synth.add( ugen = new SineOscillator() ); + // synth.add( ugen = new SubtractiveSynthVoice() ); + // Add an output mixer. + var lineOut = new LineOut(); + synth.add(lineOut); + + // Connect the oscillator to the left and right audio output. + voice.getOutput().connect(0, lineOut.input, 0); + voice.getOutput().connect(0, lineOut.input, 1); + + // Start synthesizer using default stereo output at 44100 Hz. + synth.start(); + + // Get synthesizer time in seconds. + double timeNow = synth.getCurrentTime(); + + // Advance to a near future time so we have a clean start. + TimeStamp timeStamp = new TimeStamp(timeNow + 0.5); + + // We only need to start the LineOut. It will pull data from the + // oscillator. + synth.startUnit(lineOut, timeStamp); + + // Schedule a note on and off. + double freq = 200.0; // hertz + double duration = 1.4; + double onTime = 1.0; + voice.noteOn(freq, 0.5, timeStamp); + voice.noteOff(timeStamp.makeRelative(onTime)); + + // Schedule this to happen a bit later. + timeStamp = timeStamp.makeRelative(duration); + freq *= 1.5; // up a perfect fifth + voice.noteOn(freq, 0.5, timeStamp); + voice.noteOff(timeStamp.makeRelative(onTime)); + + timeStamp = timeStamp.makeRelative(duration); + freq *= 4.0 / 5.0; // down a major third + voice.noteOn(freq, 0.5, timeStamp); + voice.noteOff(timeStamp.makeRelative(onTime)); + + // Sleep while the song is being generated in the background thread. + try { + LOGGER.debug("Sleep while synthesizing."); + synth.sleepUntil(timeStamp.getTime() + 2.0); + LOGGER.debug("Woke up..."); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + // Stop everything. + synth.stop(); + } + + public static void main(String[] args) { + new PlayNotes().test(); + } +} diff --git a/examples/src/main/java/com/jsyn/examples/PlayPartials.java b/examples/src/main/java/com/jsyn/examples/PlayPartials.java new file mode 100644 index 0000000..72e1d7f --- /dev/null +++ b/examples/src/main/java/com/jsyn/examples/PlayPartials.java @@ -0,0 +1,115 @@ +/* + * Copyright 2010 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.examples; + +import com.jsyn.JSyn; +import com.jsyn.Synthesizer; +import com.jsyn.unitgen.LineOut; +import com.jsyn.unitgen.LinearRamp; +import com.jsyn.unitgen.Multiply; +import com.jsyn.unitgen.SineOscillator; +import com.jsyn.unitgen.UnitOscillator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Play a enharmonic sine tones using JSyn oscillators. + * + * @author Phil Burk (C) 2010 Mobileer Inc + */ +public class PlayPartials { + + private static final Logger LOGGER = LoggerFactory.getLogger(PlayPartials.class); + + private Synthesizer synth; + private UnitOscillator[] osc; + private Multiply[] multipliers; + private LinearRamp ramp; + private LineOut lineOut; + private double[] amps = { + 0.2, 0.1, 0.3, 0.4 + }; + private double[] ratios = { + 1.0, Math.sqrt(2.0), Math.E, Math.PI + }; + + private void test() { + // Create a context for the synthesizer. + synth = JSyn.createSynthesizer(); + + // Start synthesizer using default stereo output at 44100 Hz. + synth.start(); + + // Add a stereo audio output unit. + synth.add(lineOut = new LineOut()); + synth.add(ramp = new LinearRamp()); + + // Add a tone generator. + osc = new SineOscillator[amps.length]; + multipliers = new Multiply[ratios.length]; + + for (int i = 0; i < osc.length; i++) { + // Create unit generators and store them in arrays. + synth.add(osc[i] = new SineOscillator()); + synth.add(multipliers[i] = new Multiply()); + + // Connect each oscillator to both channels of the output. + // They will be mixed automatically. + osc[i].output.connect(0, lineOut.input, 0); + osc[i].output.connect(0, lineOut.input, 1); + + // Use a multiplier to scale the output of the ramp. + // output = inputA * inputB + ramp.output.connect(multipliers[i].inputA); + multipliers[i].output.connect(osc[i].frequency); + multipliers[i].inputB.set(ratios[i]); + + osc[i].amplitude.set(amps[i]); + } + + // start ramping up + ramp.current.set(100.0); + ramp.time.set(3.0); + ramp.input.set(700.0); + + // We only need to start the LineOut. It will pull data from the + // oscillator. + lineOut.start(); + + LOGGER.debug("You should now be hearing a sine wave. ---------"); + + // Sleep while the sound is generated in the background. + try { + // Sleep for a few seconds. + synth.sleepFor(4.0); + // ramp down + ramp.input.set(100.0); + synth.sleepFor(4.0); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + LOGGER.debug("Stop playing. -------------------"); + // Stop everything. + synth.stop(); + } + + public static void main(String[] args) { + LOGGER.debug("Java version = " + System.getProperty("java.version")); + new PlayPartials().test(); + } +} diff --git a/examples/src/main/java/com/jsyn/examples/PlaySample.java b/examples/src/main/java/com/jsyn/examples/PlaySample.java new file mode 100644 index 0000000..280023e --- /dev/null +++ b/examples/src/main/java/com/jsyn/examples/PlaySample.java @@ -0,0 +1,126 @@ +/* + * Copyright 2010 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.examples; + +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; + +import com.jsyn.JSyn; +import com.jsyn.Synthesizer; +import com.jsyn.data.FloatSample; +import com.jsyn.unitgen.LineOut; +import com.jsyn.unitgen.VariableRateDataReader; +import com.jsyn.unitgen.VariableRateMonoReader; +import com.jsyn.unitgen.VariableRateStereoReader; +import com.jsyn.util.SampleLoader; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Play a sample from a WAV file using JSyn. + * + * @author Phil Burk (C) 2010 Mobileer Inc + */ +public class PlaySample { + + private static final Logger LOGGER = LoggerFactory.getLogger(PlaySample.class); + + private Synthesizer synth; + private VariableRateDataReader samplePlayer; + private LineOut lineOut; + + private void test() { + + URL sampleFile; + try { + sampleFile = new URL("http://www.softsynth.com/samples/Clarinet.wav"); + // sampleFile = new URL("http://www.softsynth.com/samples/NotHereNow22K.wav"); + } catch (MalformedURLException e2) { + e2.printStackTrace(); + return; + } + + synth = JSyn.createSynthesizer(); + + FloatSample sample; + try { + // Add an output mixer. + synth.add(lineOut = new LineOut()); + + // Load the sample and display its properties. + SampleLoader.setJavaSoundPreferred(false); + sample = SampleLoader.loadFloatSample(sampleFile); + LOGGER.debug("Sample has: channels = " + sample.getChannelsPerFrame()); + LOGGER.debug(" frames = " + sample.getNumFrames()); + LOGGER.debug(" rate = " + sample.getFrameRate()); + LOGGER.debug(" loopStart = " + sample.getSustainBegin()); + LOGGER.debug(" loopEnd = " + sample.getSustainEnd()); + + if (sample.getChannelsPerFrame() == 1) { + synth.add(samplePlayer = new VariableRateMonoReader()); + samplePlayer.output.connect(0, lineOut.input, 0); + } else if (sample.getChannelsPerFrame() == 2) { + synth.add(samplePlayer = new VariableRateStereoReader()); + samplePlayer.output.connect(0, lineOut.input, 0); + samplePlayer.output.connect(1, lineOut.input, 1); + } else { + throw new RuntimeException("Can only play mono or stereo samples."); + } + + // Start synthesizer using default stereo output at 44100 Hz. + synth.start(); + + samplePlayer.rate.set(sample.getFrameRate()); + + // We only need to start the LineOut. It will pull data from the + // sample player. + lineOut.start(); + + // We can simply queue the entire file. + // Or if it has a loop we can play the loop for a while. + if (sample.getSustainBegin() < 0) { + LOGGER.debug("queue the sample"); + samplePlayer.dataQueue.queue(sample); + } else { + LOGGER.debug("queueOn the sample"); + samplePlayer.dataQueue.queueOn(sample); + synth.sleepFor(8.0); + LOGGER.debug("queueOff the sample"); + samplePlayer.dataQueue.queueOff(sample); + } + + // Wait until the sample has finished playing. + do { + synth.sleepFor(1.0); + } while (samplePlayer.dataQueue.hasMore()); + + synth.sleepFor(0.5); + + } catch (IOException e1) { + e1.printStackTrace(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + // Stop everything. + synth.stop(); + } + + public static void main(String[] args) { + new PlaySample().test(); + } +} diff --git a/examples/src/main/java/com/jsyn/examples/PlaySampleCrossfade.java b/examples/src/main/java/com/jsyn/examples/PlaySampleCrossfade.java new file mode 100644 index 0000000..2157039 --- /dev/null +++ b/examples/src/main/java/com/jsyn/examples/PlaySampleCrossfade.java @@ -0,0 +1,188 @@ +/* + * Copyright 2010 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.examples; + +import java.awt.GridLayout; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; + +import javax.swing.JApplet; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +import com.jsyn.JSyn; +import com.jsyn.Synthesizer; +import com.jsyn.data.FloatSample; +import com.jsyn.devices.AudioDeviceFactory; +import com.jsyn.ports.QueueDataCommand; +import com.jsyn.swing.DoubleBoundedRangeModel; +import com.jsyn.swing.DoubleBoundedRangeSlider; +import com.jsyn.swing.JAppletFrame; +import com.jsyn.swing.PortControllerFactory; +import com.jsyn.unitgen.LineOut; +import com.jsyn.unitgen.VariableRateDataReader; +import com.jsyn.unitgen.VariableRateMonoReader; +import com.jsyn.unitgen.VariableRateStereoReader; +import com.jsyn.util.SampleLoader; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Play a sample from a WAV file using JSyn. Use a crossfade to play a loop at an arbitrary + * position. + * + * @author Phil Burk (C) 2010 Mobileer Inc + */ +public class PlaySampleCrossfade extends JApplet { + + private static final Logger LOGGER = LoggerFactory.getLogger(PlaySampleCrossfade.class); + + private static final double LOOP_START_FRACTION = 0.2; + private Synthesizer synth; + private VariableRateDataReader samplePlayer; + private LineOut lineOut; + private FloatSample sample; + private DoubleBoundedRangeModel rangeModelSize; + private DoubleBoundedRangeModel rangeModelCrossfade; + private int loopStartFrame; + + @Override + public void init() { + + URL sampleFile; + try { + sampleFile = new URL("http://www.softsynth.com/samples/Clarinet.wav"); + } catch (MalformedURLException e2) { + e2.printStackTrace(); + return; + } + + synth = JSyn.createSynthesizer(AudioDeviceFactory.createAudioDeviceManager(true)); + + try { + // Add an output mixer. + synth.add(lineOut = new LineOut()); + + // Load the sample and display its properties. + SampleLoader.setJavaSoundPreferred(false); + sample = SampleLoader.loadFloatSample(sampleFile); + LOGGER.debug("Sample has: channels = " + sample.getChannelsPerFrame()); + LOGGER.debug(" frames = " + sample.getNumFrames()); + LOGGER.debug(" rate = " + sample.getFrameRate()); + LOGGER.debug(" loopStart = " + sample.getSustainBegin()); + LOGGER.debug(" loopEnd = " + sample.getSustainEnd()); + + if (sample.getChannelsPerFrame() == 1) { + synth.add(samplePlayer = new VariableRateMonoReader()); + samplePlayer.output.connect(0, lineOut.input, 0); + } else if (sample.getChannelsPerFrame() == 2) { + synth.add(samplePlayer = new VariableRateStereoReader()); + samplePlayer.output.connect(0, lineOut.input, 0); + samplePlayer.output.connect(1, lineOut.input, 1); + } else { + throw new RuntimeException("Can only play mono or stereo samples."); + } + + samplePlayer.rate.set(sample.getFrameRate()); + + } catch (IOException e1) { + e1.printStackTrace(); + } + + // Start at arbitrary position near beginning of sample. + loopStartFrame = (int) (sample.getNumFrames() * LOOP_START_FRACTION); + + // Arrange the faders in a stack. + setLayout(new GridLayout(0, 1)); + + samplePlayer.rate.setup(4000.0, sample.getFrameRate(), sample.getFrameRate() * 2.0); + add(PortControllerFactory.createExponentialPortSlider(samplePlayer.rate)); + + // Use fader to select arbitrary loop size. + rangeModelSize = new DoubleBoundedRangeModel("LoopSize", 10000, 0.01, + (1.0 - LOOP_START_FRACTION), 0.5); + rangeModelSize.addChangeListener(new ChangeListener() { + @Override + public void stateChanged(ChangeEvent e) { + queueNewLoop(); + } + + }); + add(new DoubleBoundedRangeSlider(rangeModelSize, 3)); + + // Use fader to set the size of the crossfade region. + rangeModelCrossfade = new DoubleBoundedRangeModel("Crossfade", 1000, 0.0, 1000.0, 0.0); + rangeModelCrossfade.addChangeListener(new ChangeListener() { + @Override + public void stateChanged(ChangeEvent e) { + queueNewLoop(); + } + + }); + + add(new DoubleBoundedRangeSlider(rangeModelCrossfade, 3)); + + validate(); + } + + private void queueNewLoop() { + int loopSize = (int) (sample.getNumFrames() * rangeModelSize.getDoubleValue()); + if ((loopStartFrame + loopSize) > sample.getNumFrames()) { + loopSize = sample.getNumFrames() - loopStartFrame; + } + int crossFadeSize = (int) (rangeModelCrossfade.getDoubleValue()); + + // For complex queuing operations, create a command and then customize it. + QueueDataCommand command = samplePlayer.dataQueue.createQueueDataCommand(sample, + loopStartFrame, loopSize); + command.setNumLoops(-1); + command.setSkipIfOthers(true); + command.setCrossFadeIn(crossFadeSize); + + LOGGER.debug("Queue: " + loopStartFrame + ", #" + loopSize + ", X=" + crossFadeSize); + synth.queueCommand(command); + } + + @Override + public void start() { + // Start synthesizer using default stereo output at 44100 Hz. + synth.start(); + // Start the LineOut. It will pull data from the oscillator. + lineOut.start(); + + // Queue attack portion of sample. + samplePlayer.dataQueue.queue(sample, 0, loopStartFrame); + queueNewLoop(); + } + + @Override + public void stop() { + synth.stop(); + synth.stop(); + } + + /* Can be run as either an application or as an applet. */ + public static void main(String[] args) { + PlaySampleCrossfade applet = new PlaySampleCrossfade(); + JAppletFrame frame = new JAppletFrame("PlaySampleCrossfade", applet); + frame.setSize(440, 300); + frame.setVisible(true); + frame.test(); + } + +} diff --git a/examples/src/main/java/com/jsyn/examples/PlaySampleWaveShaper.java b/examples/src/main/java/com/jsyn/examples/PlaySampleWaveShaper.java new file mode 100644 index 0000000..3ac320b --- /dev/null +++ b/examples/src/main/java/com/jsyn/examples/PlaySampleWaveShaper.java @@ -0,0 +1,116 @@ +/* + * Copyright 2010 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.examples; + +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; + +import com.jsyn.JSyn; +import com.jsyn.Synthesizer; +import com.jsyn.data.FloatSample; +import com.jsyn.unitgen.FunctionEvaluator; +import com.jsyn.unitgen.LineOut; +import com.jsyn.unitgen.SineOscillator; +import com.jsyn.util.SampleLoader; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Play a sample from a WAV file using JSyn. + * + * @author Phil Burk (C) 2010 Mobileer Inc + */ +public class PlaySampleWaveShaper { + + private static final Logger LOGGER = LoggerFactory.getLogger(PlaySampleWaveShaper.class); + + private Synthesizer synth; + private LineOut lineOut; + + private void test() { + + URL sampleFile; + try { + sampleFile = new URL("http://www.softsynth.com/samples/Clarinet.wav"); + // sampleFile = new URL("http://www.softsynth.com/samples/NotHereNow22K.wav"); + } catch (MalformedURLException e2) { + e2.printStackTrace(); + return; + } + + synth = JSyn.createSynthesizer(); + + FloatSample sample; + try { + // Add an output mixer. + synth.add(lineOut = new LineOut()); + + // Load the sample and display its properties. + SampleLoader.setJavaSoundPreferred(false); + sample = SampleLoader.loadFloatSample(sampleFile); + LOGGER.debug("Sample has: channels = " + sample.getChannelsPerFrame()); + LOGGER.debug(" frames = " + sample.getNumFrames()); + LOGGER.debug(" rate = " + sample.getFrameRate()); + LOGGER.debug(" loopStart = " + sample.getSustainBegin()); + LOGGER.debug(" loopEnd = " + sample.getSustainEnd()); + + if (sample.getChannelsPerFrame() != 1) { + throw new RuntimeException("Can only use mono samples."); + } + + LOGGER.debug("eval -1.1 = " + sample.evaluate(-1.1)); + LOGGER.debug("eval -1.0 = " + sample.evaluate(-1.0)); + LOGGER.debug("eval 0.3 = " + sample.evaluate(0.3)); + LOGGER.debug("eval 1.0 = " + sample.evaluate(1.0)); + LOGGER.debug("eval 1.1 = " + sample.evaluate(1.1)); + + FunctionEvaluator shaper = new FunctionEvaluator(); + shaper.function.set(sample); + synth.add(shaper); + + shaper.output.connect(0, lineOut.input, 0); + shaper.output.connect(0, lineOut.input, 1); + + SineOscillator osc = new SineOscillator(); + osc.frequency.set(0.2); + osc.output.connect(shaper.input); + synth.add(osc); + + // Start synthesizer using default stereo output at 44100 Hz. + synth.start(); + + // We only need to start the LineOut. It will pull data from the + // sample player. + lineOut.start(); + + // Wait until the sample has finished playing. + synth.sleepFor(5.0); + + } catch (IOException e1) { + e1.printStackTrace(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + // Stop everything. + synth.stop(); + } + + public static void main(String[] args) { + new PlaySampleWaveShaper().test(); + } +} diff --git a/examples/src/main/java/com/jsyn/examples/PlaySegmentedEnvelope.java b/examples/src/main/java/com/jsyn/examples/PlaySegmentedEnvelope.java new file mode 100644 index 0000000..b7fb217 --- /dev/null +++ b/examples/src/main/java/com/jsyn/examples/PlaySegmentedEnvelope.java @@ -0,0 +1,156 @@ +/* + * Copyright 2010 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.examples; + +import java.io.File; +import java.io.IOException; + +import com.jsyn.JSyn; +import com.jsyn.Synthesizer; +import com.jsyn.data.SegmentedEnvelope; +import com.jsyn.unitgen.LineOut; +import com.jsyn.unitgen.SawtoothOscillatorBL; +import com.jsyn.unitgen.UnitOscillator; +import com.jsyn.unitgen.VariableRateDataReader; +import com.jsyn.unitgen.VariableRateMonoReader; +import com.jsyn.util.WaveRecorder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Modulate the amplitude of an oscillator using a segmented envelope. + * + * @author Phil Burk (C) 2010 Mobileer Inc + */ +public class PlaySegmentedEnvelope { + + private static final Logger LOGGER = LoggerFactory.getLogger(PlaySegmentedEnvelope.class); + + private Synthesizer synth; + private UnitOscillator osc; + private LineOut lineOut; + private SegmentedEnvelope envelope; + private VariableRateDataReader envelopePlayer; + private WaveRecorder recorder; + private final static boolean useRecorder = true; + + private void test() throws IOException { + // Create a context for the synthesizer. + synth = JSyn.createSynthesizer(); + // Add a tone generator. + synth.add(osc = new SawtoothOscillatorBL()); + // Add an envelope player. + synth.add(envelopePlayer = new VariableRateMonoReader()); + + if (useRecorder) { + File waveFile = new File("temp_recording.wav"); + // Default is stereo, 16 bits. + recorder = new WaveRecorder(synth, waveFile); + LOGGER.debug("Writing to WAV file " + waveFile.getAbsolutePath()); + } + + // Create an envelope consisting of (duration,value) pairs. + double[] pairs = { + 0.1, 1.0, 0.2, 0.3, 0.6, 0.0 + }; + envelope = new SegmentedEnvelope(pairs); + + // Add an output mixer. + synth.add(lineOut = new LineOut()); + envelopePlayer.output.connect(osc.amplitude); + // Connect the oscillator to the output. + osc.output.connect(0, lineOut.input, 0); + osc.output.connect(0, lineOut.input, 1); + + // Start synthesizer using default stereo output at 44100 Hz. + synth.start(); + + if (useRecorder) { + osc.output.connect(0, recorder.getInput(), 0); + envelopePlayer.output.connect(0, recorder.getInput(), 1); + // When we start the recorder it will pull data from the oscillator + // and sweeper. + recorder.start(); + } + + // We only need to start the LineOut. It will pull data from the other + // units. + lineOut.start(); + + try { + // --------------------------------------------- + // Queue the entire envelope to play once. + envelopePlayer.dataQueue.queue(envelope); + synth.sleepFor(2.0); + + // --------------------------------------------- + // Queue the attack, then sustain for a while, then queue the + // release. + osc.frequency.set(750.0); + envelopePlayer.dataQueue.queue(envelope, 0, 2); // attack + synth.sleepFor(2.0); + envelopePlayer.dataQueue.queue(envelope, 2, 1); // release + synth.sleepFor(2.0); + + // --------------------------------------------- + // Queue the attack, then sustain for a while, then queue the + // release. But this time use the sustain loop points. + osc.frequency.set(950.0); + // For noteOn, we want to play frames 0 and 1 then stop before 2. + envelope.setSustainBegin(2); + envelope.setSustainEnd(2); + envelopePlayer.dataQueue.queueOn(envelope); // attack + synth.sleepFor(2.0); + envelopePlayer.dataQueue.queueOff(envelope); // release + synth.sleepFor(2.0); + + // --------------------------------------------- + // Queue the entire envelope to play 4 times (3 loops back). + osc.frequency.set(350.0); + envelopePlayer.dataQueue.queueLoop(envelope, 0, envelope.getNumFrames(), 3); + synth.sleepFor(5.0); + + // --------------------------------------------- + // Queue the entire envelope as a repeating loop. + // It will loop until something else is queued. + osc.frequency.set(450.0); + envelopePlayer.dataQueue.queueLoop(envelope, 0, envelope.getNumFrames()); + envelopePlayer.rate.set(3.0); + synth.sleepFor(5.0); + // Queue last frame to stop the looping. + envelopePlayer.dataQueue.queue(envelope, envelope.getNumFrames() - 1, 1); + synth.sleepFor(1.0); + + if (recorder != null) { + recorder.stop(); + recorder.close(); + } + } catch (InterruptedException e) { + e.printStackTrace(); + } + // Stop everything. + synth.stop(); + } + + public static void main(String[] args) { + try { + new PlaySegmentedEnvelope().test(); + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/examples/src/main/java/com/jsyn/examples/PlaySegmentedEnvelopeCallback.java b/examples/src/main/java/com/jsyn/examples/PlaySegmentedEnvelopeCallback.java new file mode 100644 index 0000000..d8f4ff3 --- /dev/null +++ b/examples/src/main/java/com/jsyn/examples/PlaySegmentedEnvelopeCallback.java @@ -0,0 +1,119 @@ +/* + * Copyright 2010 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.examples; + +import com.jsyn.JSyn; +import com.jsyn.Synthesizer; +import com.jsyn.data.SegmentedEnvelope; +import com.jsyn.ports.QueueDataCommand; +import com.jsyn.ports.QueueDataEvent; +import com.jsyn.ports.UnitDataQueueCallback; +import com.jsyn.unitgen.LineOut; +import com.jsyn.unitgen.SawtoothOscillatorBL; +import com.jsyn.unitgen.UnitOscillator; +import com.jsyn.unitgen.VariableRateDataReader; +import com.jsyn.unitgen.VariableRateMonoReader; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Use a UnitDataQueueCallback to notify us of the envelope's progress. Modulate the amplitude of an + * oscillator using a segmented envelope. + * + * @author Phil Burk (C) 2010 Mobileer Inc + */ +public class PlaySegmentedEnvelopeCallback { + + private static final Logger LOGGER = LoggerFactory.getLogger(PlaySegmentedEnvelopeCallback.class); + + private Synthesizer synth; + private UnitOscillator osc; + private LineOut lineOut; + private SegmentedEnvelope envelope; + private VariableRateDataReader envelopePlayer; + + private void test() { + // Create a context for the synthesizer. + synth = JSyn.createSynthesizer(); + // Add a tone generator. + synth.add(osc = new SawtoothOscillatorBL()); + // Add an envelope player. + synth.add(envelopePlayer = new VariableRateMonoReader()); + + // Create an envelope consisting of (duration,value) pairs. + double[] pairs = { + 0.1, 1.0, 0.2, 0.5, 0.6, 0.0 + }; + envelope = new SegmentedEnvelope(pairs); + + // Add an output mixer. + synth.add(lineOut = new LineOut()); + envelopePlayer.output.connect(osc.amplitude); + // Connect the oscillator to the output. + osc.output.connect(0, lineOut.input, 0); + osc.output.connect(0, lineOut.input, 1); + + // Start synthesizer using default stereo output at 44100 Hz. + synth.start(); + // We only need to start the LineOut. It will pull data from the other + // units. + lineOut.start(); + + try { + // Queue an envelope with callbacks. + QueueDataCommand command = envelopePlayer.dataQueue.createQueueDataCommand(envelope, 0, + envelope.getNumFrames()); + // Create an object to be called when the queued data is done. + TestQueueCallback callback = new TestQueueCallback(); + command.setCallback(callback); + command.setNumLoops(2); + envelopePlayer.rate.set(0.2); + synth.queueCommand(command); + synth.sleepFor(20.0); + + } catch (InterruptedException e) { + e.printStackTrace(); + } + // Stop everything. + synth.stop(); + } + + class TestQueueCallback implements UnitDataQueueCallback { + @Override + public void started(QueueDataEvent event) { + LOGGER.debug("CALLBACK: Envelope started."); + } + + @Override + public void looped(QueueDataEvent event) { + LOGGER.debug("CALLBACK: Envelope looped."); + } + + @Override + public void finished(QueueDataEvent event) { + LOGGER.debug("CALLBACK: Envelope finished."); + // Queue the envelope again at a faster rate. + // (If this hangs we may have hit a deadlock.) + envelopePlayer.rate.set(2.0); + envelopePlayer.dataQueue.queue(envelope); + } + } + + public static void main(String[] args) { + new PlaySegmentedEnvelopeCallback().test(); + } +} diff --git a/examples/src/main/java/com/jsyn/examples/PlaySequence.java b/examples/src/main/java/com/jsyn/examples/PlaySequence.java new file mode 100644 index 0000000..9d058b2 --- /dev/null +++ b/examples/src/main/java/com/jsyn/examples/PlaySequence.java @@ -0,0 +1,85 @@ +/* + * 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.examples; + +import com.jsyn.JSyn; +import com.jsyn.Synthesizer; +import com.jsyn.unitgen.LineOut; +import com.jsyn.unitgen.SawtoothOscillatorBL; +import com.jsyn.unitgen.UnitOscillator; + +/** + * Use time stamps to change the frequency of an oscillator at precise times. + * + * @author Phil Burk (C) 2009 Mobileer Inc + */ +public class PlaySequence { + Synthesizer synth; + UnitOscillator osc; + LineOut lineOut; + + private void test() { + // Create a context for the synthesizer. + synth = JSyn.createSynthesizer(); + + // Add a tone generator. + synth.add(osc = new SawtoothOscillatorBL()); + // Add an output mixer. + synth.add(lineOut = new LineOut()); + + // Connect the oscillator to the left and right audio output. + osc.output.connect(0, lineOut.input, 0); + osc.output.connect(0, lineOut.input, 1); + + // Start synthesizer using default stereo output at 44100 Hz. + synth.start(); + // We only need to start the LineOut. It will pull data from the + // oscillator. + lineOut.start(); + + // Get synthesizer time in seconds. + double timeNow = synth.getCurrentTime(); + + // Advance to a near future time so we have a clean start. + double time = timeNow + 0.5; + double freq = 400.0; // hertz + osc.frequency.set(freq, time); + + // Schedule this to happen a bit later. + time += 0.5; + freq *= 1.5; // up a perfect fifth + osc.frequency.set(freq, time); + + time += 0.5; + freq *= 4.0 / 5.0; // down a major third + osc.frequency.set(freq, time); + + // Sleep while the sound is being generated in the background thread. + try { + synth.sleepUntil(time + 0.5); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + // Stop everything. + synth.stop(); + } + + public static void main(String[] args) { + new PlaySequence().test(); + } +} diff --git a/examples/src/main/java/com/jsyn/examples/PlayTone.java b/examples/src/main/java/com/jsyn/examples/PlayTone.java new file mode 100644 index 0000000..e514dba --- /dev/null +++ b/examples/src/main/java/com/jsyn/examples/PlayTone.java @@ -0,0 +1,81 @@ +/* + * Copyright 2010 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.examples; + +import com.jsyn.JSyn; +import com.jsyn.unitgen.LineOut; +import com.jsyn.unitgen.SineOscillator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Play a tone using a JSyn oscillator. + * + * @author Phil Burk (C) 2010 Mobileer Inc + */ +public class PlayTone { + + private static final Logger LOGGER = LoggerFactory.getLogger(PlayTone.class); + + private void test() { + + // Create a context for the synthesizer. + var synth = JSyn.createSynthesizer(); + + // Start synthesizer using default stereo output at 44100 Hz. + synth.start(); + + // Add a tone generator. + var oscillator = new SineOscillator(); + synth.add(oscillator); + // Add a stereo audio output unit. + var lineOut = new LineOut(); + synth.add(lineOut); + + // Connect the oscillator to both channels of the output. + oscillator.output.connect(0, lineOut.input, 0); + oscillator.output.connect(0, lineOut.input, 1); + + // Set the frequency and amplitude for the sine wave. + oscillator.frequency.set(345.0); + oscillator.amplitude.set(0.6); + + // We only need to start the LineOut. It will pull data from the + // oscillator. + lineOut.start(); + + LOGGER.debug("You should now be hearing a sine wave. ---------"); + + // Sleep while the sound is generated in the background. + try { + double time = synth.getCurrentTime(); + LOGGER.debug("time = " + time); + // Sleep for a few seconds. + synth.sleepUntil(time + 4.0); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + LOGGER.debug("Stop playing. -------------------"); + // Stop everything. + synth.stop(); + } + + public static void main(String[] args) { + new PlayTone().test(); + } +} diff --git a/examples/src/main/java/com/jsyn/examples/RecordSineSweep.java b/examples/src/main/java/com/jsyn/examples/RecordSineSweep.java new file mode 100644 index 0000000..13161c6 --- /dev/null +++ b/examples/src/main/java/com/jsyn/examples/RecordSineSweep.java @@ -0,0 +1,141 @@ +/* + * Copyright 2010 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. + */ +/** + * Test recording to disk in non-real-time. + * Play several frequencies of a sine wave. + * Save data in a WAV file format. + * + * @author (C) 2010 Phil Burk + */ + +package com.jsyn.examples; + +import java.io.File; +import java.io.IOException; + +import com.jsyn.JSyn; +import com.jsyn.Synthesizer; +import com.jsyn.unitgen.ExponentialRamp; +import com.jsyn.unitgen.LineOut; +import com.jsyn.unitgen.SawtoothOscillatorBL; +import com.jsyn.unitgen.SineOscillator; +import com.jsyn.unitgen.UnitOscillator; +import com.jsyn.util.WaveRecorder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class RecordSineSweep { + + private static final Logger LOGGER = LoggerFactory.getLogger(RecordSineSweep.class); + + final static double SONG_DURATION = 4.0; + private Synthesizer synth; + private UnitOscillator leftOsc; + private UnitOscillator rightOsc; + private ExponentialRamp sweeper; + private LineOut lineOut; + private WaveRecorder recorder; + private final static boolean useRecorder = true; + + private void test() throws IOException { + // Create a context for the synthesizer. + synth = JSyn.createSynthesizer(); + synth.setRealTime(false); + + if (useRecorder) { + File waveFile = new File("temp_recording.wav"); + // Default is stereo, 16 bits. + recorder = new WaveRecorder(synth, waveFile); + LOGGER.debug("Writing to WAV file " + waveFile.getAbsolutePath()); + } + // Add some tone generators. + synth.add(leftOsc = new SineOscillator()); + synth.add(rightOsc = new SawtoothOscillatorBL()); + + // Add a controller that will sweep up. + synth.add(sweeper = new ExponentialRamp()); + // Add an output unit. + synth.add(lineOut = new LineOut()); + + sweeper.current.set(50.0); + sweeper.input.set(1400.0); + sweeper.time.set(SONG_DURATION); + sweeper.output.connect(leftOsc.frequency); + sweeper.output.connect(rightOsc.frequency); + + // Connect the oscillator to the left and right audio output. + leftOsc.output.connect(0, lineOut.input, 0); + rightOsc.output.connect(0, lineOut.input, 1); + + // Start synthesizer using default stereo output at 44100 Hz. + synth.start(); + + if (useRecorder) { + leftOsc.output.connect(0, recorder.getInput(), 0); + rightOsc.output.connect(0, recorder.getInput(), 1); + // When we start the recorder it will pull data from the oscillator + // and sweeper. + recorder.start(); + } + + // We also need to start the LineOut if we want to hear it now. + lineOut.start(); + + // Get synthesizer time in seconds. + double timeNow = synth.getCurrentTime(); + + // Sleep while the sound is being generated in the background thread. + try { + synth.sleepUntil(timeNow + SONG_DURATION); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + // Test stopping and restarting a recorder. This will cause a pop. + if (recorder != null) { + LOGGER.debug("Stop and restart recorder."); + recorder.stop(); + } + sweeper.input.set(100.0); + timeNow = synth.getCurrentTime(); + if (recorder != null) { + recorder.start(); + } + + // Sleep while the sound is being generated in the background thread. + try { + synth.sleepUntil(timeNow + SONG_DURATION); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + if (recorder != null) { + recorder.stop(); + recorder.close(); + } + // Stop everything. + synth.stop(); + } + + public static void main(String[] args) { + try { + new RecordSineSweep().test(); + } catch (IOException e) { + e.printStackTrace(); + } + } + +} diff --git a/examples/src/main/java/com/jsyn/examples/SampleHoldNoteBlaster.java b/examples/src/main/java/com/jsyn/examples/SampleHoldNoteBlaster.java new file mode 100644 index 0000000..bc6b4d0 --- /dev/null +++ b/examples/src/main/java/com/jsyn/examples/SampleHoldNoteBlaster.java @@ -0,0 +1,153 @@ +/* + * Copyright 2011 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.examples; + +import com.jsyn.ports.UnitInputPort; +import com.jsyn.ports.UnitOutputPort; +import com.jsyn.unitgen.Circuit; +import com.jsyn.unitgen.EdgeDetector; +import com.jsyn.unitgen.EnvelopeDAHDSR; +import com.jsyn.unitgen.FilterLowPass; +import com.jsyn.unitgen.Latch; +import com.jsyn.unitgen.Multiply; +import com.jsyn.unitgen.PassThrough; +import com.jsyn.unitgen.PulseOscillator; +import com.jsyn.unitgen.SawtoothOscillatorDPW; +import com.jsyn.unitgen.SineOscillator; +import com.jsyn.unitgen.UnitOscillator; +import com.jsyn.unitgen.UnitSource; + +/** + * Classic osc-filter-envelope voice with a sample and hold. + * + * @author Phil Burk (C) 2011 Mobileer Inc + */ +public class SampleHoldNoteBlaster extends Circuit implements UnitSource { + + public UnitInputPort frequency; + public UnitInputPort amplitude; + public UnitInputPort modRate; + public UnitInputPort modDepth; + private UnitInputPort cutoff; + private UnitInputPort resonance; + private UnitInputPort pulseRate; + private UnitInputPort sweepRate; + private UnitInputPort sweepDepth; + public UnitOutputPort output; + + private static SampleHoldNoteBlaster soundMaker; // singleton + + private UnitOscillator osc; + private UnitOscillator samplee; // for sample and hold + private PulseOscillator pulser; + private Latch latch; + private UnitOscillator lfo; + private FilterLowPass filter; + private PassThrough frequencyPin; + private Multiply modScaler; + private EnvelopeDAHDSR ampEnv; + private Multiply sweepScaler; + private EdgeDetector edgeDetector; + private UnitInputPort pulseWidth; + private UnitInputPort attack; + private UnitInputPort decay; + private UnitInputPort sustain; + private UnitInputPort release; + + public static SampleHoldNoteBlaster getInstance() { + if (soundMaker == null) { + soundMaker = new SampleHoldNoteBlaster(); + } + return soundMaker; + } + + public SampleHoldNoteBlaster() { + add(frequencyPin = new PassThrough()); + add(modScaler = new Multiply()); + add(sweepScaler = new Multiply()); + add(edgeDetector = new EdgeDetector()); + add(latch = new Latch()); + add(samplee = new SineOscillator()); + add(pulser = new PulseOscillator()); + add(lfo = new SineOscillator()); + add(osc = new SawtoothOscillatorDPW()); + add(filter = new FilterLowPass()); + // Use an envelope to control the amplitude. + add(ampEnv = new EnvelopeDAHDSR()); + + samplee.output.connect(latch.input); + pulser.output.connect(edgeDetector.input); + edgeDetector.output.connect(latch.gate); + latch.output.connect(osc.frequency); + + frequencyPin.output.connect(osc.frequency); + + frequencyPin.output.connect(modScaler.inputA); + modScaler.output.connect(lfo.amplitude); + + frequencyPin.output.connect(sweepScaler.inputA); + sweepScaler.output.connect(samplee.amplitude); + + lfo.output.connect(osc.frequency); + osc.output.connect(filter.input); + filter.output.connect(ampEnv.amplitude); + pulser.output.connect(ampEnv.input); + + // Setup ports --------------- + addPort(amplitude = osc.amplitude, "amplitude"); + amplitude.set(0.6); + + addPort(frequency = frequencyPin.input, "frequency"); + frequency.setup(50.0, 800.0, 2000.0); + + addPort(modRate = lfo.frequency, "modRate"); + modRate.setup(0.0, 12, 20.0); + + addPort(modDepth = modScaler.inputB, "modDepth"); + modDepth.setup(0.0, 0.0, 0.5); + + addPort(cutoff = filter.frequency, "cutoff"); + cutoff.setup(20.0, 2000.0, 5000.0); + addPort(resonance = filter.Q, "Q"); + + addPort(sweepDepth = sweepScaler.inputB, "sweepDepth"); + sweepDepth.setup(0.0, 0.6, 1.0); + addPort(sweepRate = samplee.frequency, "sweepRate"); + sweepRate.setup(0.2, 5.9271, 20.0); + + addPort(pulseRate = pulser.frequency, "pulseRate"); + pulseRate.setup(0.2, 7.0, 20.0); + addPort(pulseWidth = pulser.width, "pulseWidth"); + pulseWidth.setup(-0.9, 0.9, 0.9); + + addPort(attack = ampEnv.attack, "attack"); + attack.setup(0.001, 0.001, 2.0); + addPort(decay = ampEnv.decay, "decay"); + decay.setup(0.001, 0.26, 2.0); + addPort(sustain = ampEnv.sustain, "sustain"); + sustain.setup(0.000, 0.24, 1.0); + addPort(release = ampEnv.release, "release"); + release.setup(0.001, 0.2, 2.0); + + addPort(output = ampEnv.output); + } + + @Override + public UnitOutputPort getOutput() { + return output; + } +} diff --git a/examples/src/main/java/com/jsyn/examples/SawFaders.java b/examples/src/main/java/com/jsyn/examples/SawFaders.java new file mode 100644 index 0000000..f7ce555 --- /dev/null +++ b/examples/src/main/java/com/jsyn/examples/SawFaders.java @@ -0,0 +1,103 @@ +/* + * Copyright 2010 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.examples; + +import java.awt.GridLayout; + +import javax.swing.JApplet; +import javax.swing.JPanel; + +import com.jsyn.JSyn; +import com.jsyn.Synthesizer; +import com.jsyn.swing.ExponentialRangeModel; +import com.jsyn.swing.JAppletFrame; +import com.jsyn.swing.PortControllerFactory; +import com.jsyn.swing.PortModelFactory; +import com.jsyn.swing.RotaryTextController; +import com.jsyn.unitgen.LineOut; +import com.jsyn.unitgen.LinearRamp; +import com.jsyn.unitgen.SawtoothOscillatorBL; +import com.jsyn.unitgen.UnitOscillator; + +/** + * Play a sawtooth using a JSyn oscillator and some knobs. + * + * @author Phil Burk (C) 2010 Mobileer Inc + */ +public class SawFaders extends JApplet { + private Synthesizer synth; + private UnitOscillator osc; + private LinearRamp lag; + private LineOut lineOut; + + @Override + public void init() { + synth = JSyn.createSynthesizer(); + + // Add a tone generator. (band limited sawtooth) + synth.add(osc = new SawtoothOscillatorBL()); + // Add a lag to smooth out amplitude changes and avoid pops. + synth.add(lag = new LinearRamp()); + // Add an output mixer. + synth.add(lineOut = new LineOut()); + // Connect the oscillator to both left and right output. + osc.output.connect(0, lineOut.input, 0); + osc.output.connect(0, lineOut.input, 1); + + // Set the minimum, current and maximum values for the port. + lag.output.connect(osc.amplitude); + lag.input.setup(0.0, 0.5, 1.0); + lag.time.set(0.2); + + // Arrange the faders in a stack. + setLayout(new GridLayout(0, 1)); + + ExponentialRangeModel amplitudeModel = PortModelFactory.createExponentialModel(lag.input); + RotaryTextController knob = new RotaryTextController(amplitudeModel, 5); + JPanel knobPanel = new JPanel(); + knobPanel.add(knob); + add(knobPanel); + + osc.frequency.setup(50.0, 300.0, 10000.0); + add(PortControllerFactory.createExponentialPortSlider(osc.frequency)); + validate(); + } + + @Override + public void start() { + // Start synthesizer using default stereo output at 44100 Hz. + synth.start(); + // We only need to start the LineOut. It will pull data from the + // oscillator. + lineOut.start(); + } + + @Override + public void stop() { + synth.stop(); + } + + /* Can be run as either an application or as an applet. */ + public static void main(String[] args) { + SawFaders applet = new SawFaders(); + JAppletFrame frame = new JAppletFrame("SawFaders", applet); + frame.setSize(440, 200); + frame.setVisible(true); + frame.test(); + } + +} diff --git a/examples/src/main/java/com/jsyn/examples/SeeGoogleWave.java b/examples/src/main/java/com/jsyn/examples/SeeGoogleWave.java new file mode 100644 index 0000000..ff41e25 --- /dev/null +++ b/examples/src/main/java/com/jsyn/examples/SeeGoogleWave.java @@ -0,0 +1,110 @@ +/* + * Copyright 2010 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.examples; + +import java.awt.BorderLayout; +import java.awt.GridLayout; + +import javax.swing.JApplet; +import javax.swing.JLabel; +import javax.swing.JPanel; + +import com.jsyn.JSyn; +import com.jsyn.Synthesizer; +import com.jsyn.scope.AudioScope; +import com.jsyn.swing.JAppletFrame; +import com.jsyn.swing.PortControllerFactory; +import com.jsyn.unitgen.LineOut; + +/** + * Generate the waveform shown on the Google home page on 2/22/12. + * + * @author Phil Burk (C) 2010 Mobileer Inc + */ +public class SeeGoogleWave extends JApplet { + private Synthesizer synth; + private GoogleWaveOscillator googleWaveUnit; + private LineOut lineOut; + private AudioScope scope; + + /* Can be run as either an application or as an applet. */ + public static void main(String[] args) { + SeeGoogleWave applet = new SeeGoogleWave(); + JAppletFrame frame = new JAppletFrame("Google Wave", applet); + frame.setSize(640, 500); + frame.setVisible(true); + frame.test(); + frame.validate(); + } + + private void setupGUI() { + setLayout(new BorderLayout()); + + add(BorderLayout.NORTH, new JLabel("GoogleWave - elliptical segments")); + + scope = new AudioScope(synth); + scope.addProbe(googleWaveUnit.output); + scope.setTriggerMode(AudioScope.TriggerMode.NORMAL); + scope.getView().setShowControls(false); + scope.start(); + add(BorderLayout.CENTER, scope.getView()); + + JPanel southPanel = new JPanel(); + southPanel.setLayout(new GridLayout(0, 1)); + add(BorderLayout.SOUTH, southPanel); + + southPanel.add(PortControllerFactory.createExponentialPortSlider(googleWaveUnit.frequency)); + southPanel.add(PortControllerFactory.createExponentialPortSlider(googleWaveUnit.variance)); + southPanel.add(PortControllerFactory.createExponentialPortSlider(googleWaveUnit.amplitude)); + + validate(); + } + + @Override + public void start() { + synth = JSyn.createSynthesizer(); + synth.add(googleWaveUnit = new GoogleWaveOscillator()); + googleWaveUnit.amplitude.setup(0.02, 0.5, 1.0); + googleWaveUnit.variance.setup(0.0, 0.0, 1.0); + googleWaveUnit.frequency.setup(40.0, 200.0, 1000.0); + + // Add an output so we can hear it. + synth.add(lineOut = new LineOut()); + + googleWaveUnit.output.connect(0, lineOut.input, 0); + googleWaveUnit.output.connect(0, lineOut.input, 1); + + setupGUI(); + + // Start synthesizer using default stereo output at 44100 Hz. + synth.start(); + // Start lineOut so it can pull data from other units. + lineOut.start(); + + // We only need to start the LineOut. It will pull data from the + // oscillator. + lineOut.start(); + + } + + @Override + public void stop() { + scope.stop(); + synth.stop(); + } + +} diff --git a/examples/src/main/java/com/jsyn/examples/SeeOscillators.java b/examples/src/main/java/com/jsyn/examples/SeeOscillators.java new file mode 100644 index 0000000..8cdc3b8 --- /dev/null +++ b/examples/src/main/java/com/jsyn/examples/SeeOscillators.java @@ -0,0 +1,219 @@ +/* + * Copyright 2010 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.examples; + +import java.awt.BorderLayout; +import java.awt.GridLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.ArrayList; + +import javax.swing.ButtonGroup; +import javax.swing.JApplet; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JRadioButton; + +import com.jsyn.JSyn; +import com.jsyn.Synthesizer; +import com.jsyn.scope.AudioScope; +import com.jsyn.scope.AudioScopeProbe; +import com.jsyn.swing.DoubleBoundedRangeSlider; +import com.jsyn.swing.JAppletFrame; +import com.jsyn.swing.PortControllerFactory; +import com.jsyn.unitgen.ImpulseOscillator; +import com.jsyn.unitgen.ImpulseOscillatorBL; +import com.jsyn.unitgen.LineOut; +import com.jsyn.unitgen.LinearRamp; +import com.jsyn.unitgen.MorphingOscillatorBL; +import com.jsyn.unitgen.Multiply; +import com.jsyn.unitgen.PulseOscillator; +import com.jsyn.unitgen.PulseOscillatorBL; +import com.jsyn.unitgen.RedNoise; +import com.jsyn.unitgen.SawtoothOscillator; +import com.jsyn.unitgen.SawtoothOscillatorBL; +import com.jsyn.unitgen.SawtoothOscillatorDPW; +import com.jsyn.unitgen.SineOscillator; +import com.jsyn.unitgen.SquareOscillator; +import com.jsyn.unitgen.SquareOscillatorBL; +import com.jsyn.unitgen.TriangleOscillator; +import com.jsyn.unitgen.UnitOscillator; + +/** + * Display each oscillator's waveform using the AudioScope. This is a re-implementation of the + * TJ_SeeOsc Applet from the old API. + * + * @author Phil Burk (C) 2010 Mobileer Inc + */ +public class SeeOscillators extends JApplet { + private Synthesizer synth; + private ArrayList oscillators = new ArrayList(); + private LineOut lineOut; + private AudioScope scope; + private JPanel oscPanel; + private Multiply oscGain; + private ButtonGroup buttonGroup; + private LinearRamp freqRamp; + private LinearRamp widthRamp; + private LinearRamp shapeRamp; + private DoubleBoundedRangeSlider widthSlider; + private DoubleBoundedRangeSlider shapeSlider; + + /* Can be run as either an application or as an applet. */ + public static void main(String[] args) { + SeeOscillators applet = new SeeOscillators(); + JAppletFrame frame = new JAppletFrame("ShowWaves", applet); + frame.setSize(640, 500); + frame.setVisible(true); + frame.test(); + frame.validate(); + } + + private void setupGUI() { + setLayout(new BorderLayout()); + + add(BorderLayout.NORTH, new JLabel("Show Oscillators in an AudioScope")); + + scope = new AudioScope(synth); + AudioScopeProbe probe = scope.addProbe(oscGain.output); + probe.setAutoScaleEnabled(false); + probe.setVerticalScale(1.1); + scope.setTriggerMode(AudioScope.TriggerMode.NORMAL); + // scope.getModel().getTriggerModel().getLevelModel().setDoubleValue( 0.0001 ); + // Turn off the gain and trigger control GUI. + scope.getView().setControlsVisible(false); + scope.start(); + add(BorderLayout.CENTER, scope.getView()); + + JPanel southPanel = new JPanel(); + southPanel.setLayout(new GridLayout(0, 1)); + add(BorderLayout.SOUTH, southPanel); + + oscPanel = new JPanel(); + oscPanel.setLayout(new GridLayout(2, 5)); + southPanel.add(oscPanel); + + southPanel.add(PortControllerFactory.createExponentialPortSlider(freqRamp.input)); + southPanel.add(PortControllerFactory.createExponentialPortSlider(oscGain.inputB)); + southPanel.add(widthSlider = PortControllerFactory.createPortSlider(widthRamp.input)); + widthSlider.setEnabled(false); + southPanel.add(shapeSlider = PortControllerFactory.createPortSlider(shapeRamp.input)); + shapeSlider.setEnabled(false); + + oscPanel.validate(); + validate(); + } + + @Override + public void start() { + synth = JSyn.createSynthesizer(); + + // Use a multiplier for gain control and so we can hook up to the scope + // from a single unit. + synth.add(oscGain = new Multiply()); + oscGain.inputB.setup(0.02, 0.5, 1.0); + oscGain.inputB.setName("Amplitude"); + + synth.add(freqRamp = new LinearRamp()); + freqRamp.input.setup(50.0, 300.0, 20000.0); + freqRamp.input.setName("Frequency"); + freqRamp.time.set(0.1); + + synth.add(widthRamp = new LinearRamp()); + widthRamp.input.setup(-1.0, 0.0, 1.0); + widthRamp.input.setName("Width"); + widthRamp.time.set(0.1); + + synth.add(shapeRamp = new LinearRamp()); + shapeRamp.input.setup(-1.0, 0.0, 1.0); + shapeRamp.input.setName("Shape"); + shapeRamp.time.set(0.1); + + // Add an output so we can hear the oscillators. + synth.add(lineOut = new LineOut()); + + oscGain.output.connect(0, lineOut.input, 0); + oscGain.output.connect(0, lineOut.input, 1); + + setupGUI(); + + buttonGroup = new ButtonGroup(); + + addOscillator(new SineOscillator(), "Sine"); + addOscillator(new TriangleOscillator(), "Triangle"); + addOscillator(new SawtoothOscillator(), "Sawtooth"); + addOscillator(new SawtoothOscillatorBL(), "SawBL"); + addOscillator(new SawtoothOscillatorDPW(), "SawDPW"); + addOscillator(new RedNoise(), "RedNoise"); + + addOscillator(new SquareOscillator(), "Square"); + addOscillator(new SquareOscillatorBL(), "SquareBL"); + addOscillator(new PulseOscillator(), "Pulse"); + addOscillator(new PulseOscillatorBL(), "PulseBL"); + addOscillator(new MorphingOscillatorBL(), "MorphBL"); + addOscillator(new ImpulseOscillator(), "Impulse"); + addOscillator(new ImpulseOscillatorBL(), "ImpulseBL"); + + // Start synthesizer using default stereo output at 44100 Hz. + synth.start(); + // Start lineOut so it can pull data from other units. + lineOut.start(); + + // We only need to start the LineOut. It will pull data from the + // oscillator. + lineOut.start(); + + } + + private void addOscillator(final UnitOscillator osc, String label) { + oscillators.add(osc); + synth.add(osc); + freqRamp.output.connect(osc.frequency); + if (osc instanceof PulseOscillatorBL) { + widthRamp.output.connect(((PulseOscillatorBL)osc).width); + } + if (osc instanceof PulseOscillator) { + widthRamp.output.connect(((PulseOscillator)osc).width); + } + if (osc instanceof MorphingOscillatorBL) { + shapeRamp.output.connect(((MorphingOscillatorBL)osc).shape); + } + osc.amplitude.set(1.0); + JRadioButton checkBox = new JRadioButton(label); + buttonGroup.add(checkBox); + checkBox.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent arg0) { + // Disconnect other oscillators. + oscGain.inputA.disconnectAll(0); + // Connect this one. + osc.output.connect(oscGain.inputA); + widthSlider.setEnabled(osc instanceof PulseOscillator + || osc instanceof PulseOscillatorBL); + shapeSlider.setEnabled(osc instanceof MorphingOscillatorBL); + } + }); + oscPanel.add(checkBox); + } + + @Override + public void stop() { + scope.stop(); + synth.stop(); + } + +} diff --git a/examples/src/main/java/com/jsyn/examples/ShowWaves.java b/examples/src/main/java/com/jsyn/examples/ShowWaves.java new file mode 100644 index 0000000..8ad3c4a --- /dev/null +++ b/examples/src/main/java/com/jsyn/examples/ShowWaves.java @@ -0,0 +1,120 @@ +/* + * Copyright 2010 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.examples; + +import java.awt.BorderLayout; +import java.util.ArrayList; + +import javax.swing.JApplet; +import javax.swing.JLabel; + +import com.jsyn.JSyn; +import com.jsyn.Synthesizer; +import com.jsyn.scope.AudioScope; +import com.jsyn.swing.JAppletFrame; +import com.jsyn.unitgen.Add; +import com.jsyn.unitgen.LineOut; +import com.jsyn.unitgen.SawtoothOscillatorBL; +import com.jsyn.unitgen.SineOscillator; +import com.jsyn.unitgen.TriangleOscillator; +import com.jsyn.unitgen.UnitOscillator; + +/** + * Display waveforms using the AudioScope. The frequency of the oscillators is modulated by an LFO. + * + * @author Phil Burk (C) 2010 Mobileer Inc + */ +public class ShowWaves extends JApplet { + private Synthesizer synth; + private UnitOscillator lfo; + private Add adder; + private ArrayList oscillators = new ArrayList(); + private LineOut lineOut; + private AudioScope scope; + + /* Can be run as either an application or as an applet. */ + public static void main(String[] args) { + ShowWaves applet = new ShowWaves(); + JAppletFrame frame = new JAppletFrame("ShowWaves", applet); + frame.setSize(640, 300); + frame.setVisible(true); + frame.test(); + } + + private void setupGUI() { + setLayout(new BorderLayout()); + + add(BorderLayout.NORTH, new JLabel("ShowWaves in an AudioScope Mod001")); + + scope = new AudioScope(synth); + for (UnitOscillator osc : oscillators) { + scope.addProbe(osc.output); + } + scope.setTriggerMode(AudioScope.TriggerMode.NORMAL); + scope.start(); + + // Turn on the gain and trigger control GUI. + scope.getView().setControlsVisible(true); + add(BorderLayout.CENTER, scope.getView()); + validate(); + } + + @Override + public void start() { + synth = JSyn.createSynthesizer(); + + // Add an LFO. + synth.add(lfo = new SineOscillator()); + synth.add(adder = new Add()); + + // Add an output so we can hear the oscillators. + synth.add(lineOut = new LineOut()); + + lfo.frequency.set(0.1); + lfo.amplitude.set(200.0); + adder.inputB.set(400.0); + lfo.output.connect(adder.inputA); + + oscillators.add(new SawtoothOscillatorBL()); + oscillators.add(new SineOscillator()); + oscillators.add(new TriangleOscillator()); + for (UnitOscillator osc : oscillators) { + synth.add(osc); + adder.output.connect(osc.frequency); + osc.output.connect(0, lineOut.input, 0); + osc.amplitude.set(0.2); + } + + // Start synthesizer using default stereo output at 44100 Hz. + synth.start(); + // Start lineOut so it can pull data from other units. + lineOut.start(); + setupGUI(); + + // We only need to start the LineOut. It will pull data from the + // oscillator. + lineOut.start(); + + } + + @Override + public void stop() { + scope.stop(); + synth.stop(); + } + +} diff --git a/examples/src/main/java/com/jsyn/examples/SwarmOfOscillators.java b/examples/src/main/java/com/jsyn/examples/SwarmOfOscillators.java new file mode 100644 index 0000000..9f7c19c --- /dev/null +++ b/examples/src/main/java/com/jsyn/examples/SwarmOfOscillators.java @@ -0,0 +1,146 @@ +/* + * 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.examples; + +import com.jsyn.JSyn; +import com.jsyn.Synthesizer; +import com.jsyn.ports.UnitOutputPort; +import com.jsyn.unitgen.Add; +import com.jsyn.unitgen.AsymptoticRamp; +import com.jsyn.unitgen.Circuit; +import com.jsyn.unitgen.LineOut; +import com.jsyn.unitgen.Pan; +import com.jsyn.unitgen.SawtoothOscillatorDPW; +import com.jsyn.unitgen.SineOscillator; +import com.jsyn.unitgen.UnitOscillator; +import com.jsyn.unitgen.UnitSource; + +/** + * Make a bunch of oscillators that swarm around a moving frequency. + * + * @author Phil Burk (C) 2009 Mobileer Inc + */ +public class SwarmOfOscillators { + private Synthesizer synth; + Follower[] followers; + SineOscillator lfo; + LineOut lineOut; + private Add tiePoint; + private static final int NUM_FOLLOWERS = 30; + + class Follower extends Circuit implements UnitSource { + UnitOscillator osc; + AsymptoticRamp lag; + Pan panner; + + Follower() { + // Add a tone generator. + add(osc = new SawtoothOscillatorDPW()); + osc.amplitude.set(0.03); + + // Use a lag to smoothly change frequency. + add(lag = new AsymptoticRamp()); + double hlife = 0.01 + (Math.random() * 0.9); + lag.halfLife.set(hlife); + + // Set left/right pan randomly between -1.0 and +1.0. + add(panner = new Pan()); + panner.pan.set((Math.random() * 2.0) - 1.0); + + // Track the frequency coming through the tiePoint. + tiePoint.output.connect(lag.input); + // Add the LFO offset. + lfo.output.connect(lag.input); + + lag.output.connect(osc.frequency); + + // Connect the oscillator to the left and right audio output. + osc.output.connect(panner.input); + } + + @Override + public UnitOutputPort getOutput() { + return panner.output; + } + } + + private void test() { + synth = JSyn.createSynthesizer(); + + // Add an output mixer. + synth.add(lineOut = new LineOut()); + + // Add a unit just to distribute the control frequency. + synth.add(tiePoint = new Add()); + synth.add(lfo = new SineOscillator()); + lfo.amplitude.set(40.0); + lfo.frequency.set(2.3); + + followers = new Follower[NUM_FOLLOWERS]; + for (int i = 0; i < followers.length; i++) { + Follower follower = new Follower(); + synth.add(follower); + + // Every follower can connect directly to the lineOut because all input ports are + // mixers. + follower.getOutput().connect(0, lineOut.input, 0); + follower.getOutput().connect(1, lineOut.input, 1); + + followers[i] = follower; + } + + // Start synthesizer using default stereo output at 44100 Hz. + synth.start(); + // We only need to start the LineOut. It will pull data from the + // oscillator. + lineOut.start(); + + // Get synthesizer time in seconds. + double timeNow = synth.getCurrentTime(); + + // Advance to a near future time so we have a clean start. + double duration = 0.9; + double time = timeNow + duration; + double freq = 400.0; // hertz + tiePoint.inputA.set(freq, time); + + // Randomly change the target frequency for the followers. + try { + for (int i = 0; i < 20; i++) { + // Schedule this to happen a bit later. + time += duration; + freq = 200.0 + (Math.random() * 500.0); + tiePoint.inputA.set(freq, time); + + // Sleep while the sound is being generated in the background + // thread. + synth.sleepUntil(time - 0.2); + } + } catch (InterruptedException e) { + e.printStackTrace(); + } + + System.out.format("CPU usage = %4.2f%c\n", synth.getUsage() * 100, '%'); + + // Stop everything. + synth.stop(); + } + + public static void main(String[] args) { + new SwarmOfOscillators().test(); + } +} diff --git a/examples/src/main/java/com/jsyn/examples/UseMidiKeyboard.java b/examples/src/main/java/com/jsyn/examples/UseMidiKeyboard.java new file mode 100644 index 0000000..f88b877 --- /dev/null +++ b/examples/src/main/java/com/jsyn/examples/UseMidiKeyboard.java @@ -0,0 +1,125 @@ +/* + * Copyright 2010 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.examples; + +import java.io.IOException; + +import javax.sound.midi.MidiDevice; +import javax.sound.midi.MidiMessage; +import javax.sound.midi.MidiUnavailableException; +import javax.sound.midi.Receiver; + +import com.jsyn.JSyn; +import com.jsyn.Synthesizer; +import com.jsyn.devices.javasound.MidiDeviceTools; +import com.jsyn.instruments.DualOscillatorSynthVoice; +import com.jsyn.midi.MidiSynthesizer; +import com.jsyn.unitgen.LineOut; +import com.jsyn.util.MultiChannelSynthesizer; +import com.jsyn.util.VoiceDescription; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Connect a USB MIDI Keyboard to the internal MIDI Synthesizer using JavaSound. + * + * @author Phil Burk (C) 2010 Mobileer Inc + */ +public class UseMidiKeyboard { + + private static final Logger LOGGER = LoggerFactory.getLogger(UseMidiKeyboard.class); + private static final int NUM_CHANNELS = 16; + private static final int VOICES_PER_CHANNEL = 3; + + private Synthesizer synth; + private LineOut lineOut; + private MidiSynthesizer midiSynthesizer; + private VoiceDescription voiceDescription; + private MultiChannelSynthesizer multiSynth; + + public static void main(String[] args) { + UseMidiKeyboard app = new UseMidiKeyboard(); + try { + app.test(); + } catch (MidiUnavailableException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + // Write a Receiver to get the messages from a Transmitter. + class CustomReceiver implements Receiver { + @Override + public void close() { + System.out.print("Closed."); + } + + @Override + public void send(MidiMessage message, long timeStamp) { + byte[] bytes = message.getMessage(); + midiSynthesizer.onReceive(bytes, 0, bytes.length); + } + } + + public int test() throws MidiUnavailableException, IOException, InterruptedException { + setupSynth(); + + int result = 2; + MidiDevice keyboard = MidiDeviceTools.findKeyboard(); + Receiver receiver = new CustomReceiver(); + // Just use default synthesizer. + if (keyboard != null) { + // If you forget to open them you will hear no sound. + keyboard.open(); + // Put the receiver in the transmitter. + // This gives fairly low latency playing. + keyboard.getTransmitter().setReceiver(receiver); + LOGGER.debug("Play MIDI keyboard: " + keyboard.getDeviceInfo().getDescription()); + result = 0; + } else { + LOGGER.debug("Could not find a keyboard."); + } + return result; + } + + + private void setupSynth() { + synth = JSyn.createSynthesizer(); + + voiceDescription = DualOscillatorSynthVoice.getVoiceDescription(); +// voiceDescription = SubtractiveSynthVoice.getVoiceDescription(); + + multiSynth = new MultiChannelSynthesizer(); + final int startChannel = 0; + multiSynth.setup(synth, startChannel, NUM_CHANNELS, VOICES_PER_CHANNEL, voiceDescription); + midiSynthesizer = new MidiSynthesizer(multiSynth); + + // Create a LineOut for the entire synthesizer. + synth.add(lineOut = new LineOut()); + multiSynth.getOutput().connect(0,lineOut.input, 0); + multiSynth.getOutput().connect(1,lineOut.input, 1); + + // Start synthesizer using default stereo output at 44100 Hz. + synth.start(); + lineOut.start(); + + } + +} diff --git a/examples/src/main/java/com/jsyn/examples/WindCircuit.java b/examples/src/main/java/com/jsyn/examples/WindCircuit.java new file mode 100644 index 0000000..1e3623e --- /dev/null +++ b/examples/src/main/java/com/jsyn/examples/WindCircuit.java @@ -0,0 +1,90 @@ +/* + * Copyright 1997 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.examples; + +import com.jsyn.ports.UnitInputPort; +import com.jsyn.ports.UnitOutputPort; +import com.jsyn.unitgen.Circuit; +import com.jsyn.unitgen.FilterStateVariable; +import com.jsyn.unitgen.MultiplyAdd; +import com.jsyn.unitgen.RedNoise; +import com.jsyn.unitgen.UnitSource; +import com.jsyn.unitgen.WhiteNoise; + +/** + * Wind Sound Create a wind-like sound by feeding white noise "shshshshsh" through a randomly + * varying state filter to make a "whooowhoosh" sound. The cuttoff frequency of the low pass filter + * is controlled by a RedNoise unit which creates a slowly varying random control signal. + * + * @author (C) 1997 Phil Burk, SoftSynth.com + */ + +public class WindCircuit extends Circuit implements UnitSource { + /* Declare units that will be part of the circuit. */ + WhiteNoise myNoise; + FilterStateVariable myFilter; + RedNoise myLFO; + MultiplyAdd myScalar; + + /* Declare ports. */ + public UnitInputPort noiseAmp; + public UnitInputPort modRate; + public UnitInputPort modDepth; + public UnitInputPort cutoff; + public UnitInputPort resonance; + public UnitInputPort amplitude; + public UnitOutputPort output; + + public WindCircuit() { + /* + * Create various unit generators and add them to circuit. + */ + add(myNoise = new WhiteNoise()); + add(myFilter = new FilterStateVariable()); + add(myLFO = new RedNoise()); + add(myScalar = new MultiplyAdd()); + + /* Make ports on internal units appear as ports on circuit. */ + /* Optionally give some circuit ports more meaningful names. */ + addPort(noiseAmp = myNoise.amplitude, "NoiseAmp"); + addPort(modRate = myLFO.frequency, "ModRate"); + addPort(modDepth = myScalar.inputB, "ModDepth"); + addPort(cutoff = myScalar.inputC, "Cutoff"); + addPort(resonance = myFilter.resonance); + addPort(amplitude = myFilter.amplitude); + addPort(output = myFilter.output); + + /* Connect SynthUnits to make control signal path. */ + myLFO.output.connect(myScalar.inputA); + myScalar.output.connect(myFilter.frequency); + /* Connect SynthUnits to make audio signal path. */ + myNoise.output.connect(myFilter.input); + + /* Set ports to useful values and ranges. */ + noiseAmp.setup(0.0, 0.3, 0.4); + modRate.setup(0.0, 1.0, 10.0); + modDepth.setup(0.0, 300.0, 1000.0); + cutoff.setup(0.0, 600.0, 1000.0); + resonance.setup(0.0, 0.066, 0.2); + amplitude.setup(0.0, 0.9, 0.999); + } + + @Override + public UnitOutputPort getOutput() { + return output; + } +} diff --git a/examples/src/main/resources/log4j.xml b/examples/src/main/resources/log4j.xml new file mode 100644 index 0000000..cc107b1 --- /dev/null +++ b/examples/src/main/resources/log4j.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..5c2d1cf Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..bb8b2fc --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.5.1-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..83f2acf --- /dev/null +++ b/gradlew @@ -0,0 +1,188 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# 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 +# +# https://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. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..9618d8d --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,100 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..94bd48c --- /dev/null +++ b/settings.gradle @@ -0,0 +1,2 @@ +rootProject.name = 'jysn' +include 'examples' diff --git a/src/com/jsyn/JSyn.java b/src/com/jsyn/JSyn.java deleted file mode 100644 index bbc2891..0000000 --- a/src/com/jsyn/JSyn.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright 2010 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; - -import java.sql.Date; -import java.util.GregorianCalendar; - -import com.jsyn.devices.AudioDeviceManager; -import com.jsyn.engine.SynthesisEngine; - -/** - * JSyn Synthesizer for Java. Use this factory class to create a synthesizer. This code demonstrates - * how to start playing a sine wave: - * - *

-	// Create a context for the synthesizer.
-	synth = JSyn.createSynthesizer();
-
-	// Start synthesizer using default stereo output at 44100 Hz.
-	synth.start();
-
-	// Add a tone generator.
-	synth.add( osc = new SineOscillator() );
-	// Add a stereo audio output unit.
-	synth.add( lineOut = new LineOut() );
-
-	// Connect the oscillator to both channels of the output.
-	osc.output.connect( 0, lineOut.input, 0 );
-	osc.output.connect( 0, lineOut.input, 1 );
-
-	// Set the frequency and amplitude for the sine wave.
-	osc.frequency.set( 345.0 );
-	osc.amplitude.set( 0.6 );
-
-	// We only need to start the LineOut. It will pull data from the oscillator.
-	lineOut.start();
- 
- * - * @author Phil Burk (C) 2010 Mobileer Inc - */ -public class JSyn { - // Update these for every release. - private final static int VERSION_MAJOR = 16; - private final static int VERSION_MINOR = 8; - private final static int VERSION_REVISION = 1; - public final static int BUILD_NUMBER = 464; - private final static long BUILD_TIME = new GregorianCalendar(2017, - GregorianCalendar.OCTOBER, 16).getTime().getTime(); - - public final static String VERSION = VERSION_MAJOR + "." + VERSION_MINOR + "." - + VERSION_REVISION; - public final static int VERSION_CODE = (VERSION_MAJOR << 16) + (VERSION_MINOR << 8) - + VERSION_REVISION; - public final static String VERSION_TEXT = "V" + VERSION + " (build " + BUILD_NUMBER + ", " - + (new Date(BUILD_TIME)) + ")"; - - public static Synthesizer createSynthesizer() { - return new SynthesisEngine(); - } - - public static Synthesizer createSynthesizer(AudioDeviceManager audioDeviceManager) { - return new SynthesisEngine(audioDeviceManager); - } -} diff --git a/src/com/jsyn/Synthesizer.java b/src/com/jsyn/Synthesizer.java deleted file mode 100644 index bfabb4c..0000000 --- a/src/com/jsyn/Synthesizer.java +++ /dev/null @@ -1,202 +0,0 @@ -/* - * Copyright 2010 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; - -import com.jsyn.devices.AudioDeviceManager; -import com.jsyn.unitgen.UnitGenerator; -import com.softsynth.shared.time.ScheduledCommand; -import com.softsynth.shared.time.TimeStamp; - -/** - * A synthesizer used by JSyn to generate audio. The synthesizer executes a network of unit - * generators to create an audio signal. - * - * @author Phil Burk (C) 2010 Mobileer Inc - */ -public interface Synthesizer { - - public final static int FRAMES_PER_BLOCK = 8; - - /** - * Starts a background thread that generates audio using the default frame rate of 44100 and two - * stereo output channels. - */ - public void start(); - - /** - * Starts a background thread that generates audio using the specified frame rate and two stereo - * output channels. - * - * @param frameRate in Hertz - */ - public void start(int frameRate); - - /** - * Starts the synthesizer using specific audio devices. - *

- * Note that using more than 2 channels will probably require the use of JPortAudio because - * JavaSound currently does not support more than two channels. - * JPortAudio is available at - * http://www.softsynth.com/jsyn/developers/download.php. - *

- * If you use more than 2 inputs or outputs then you will probably want to use {@link com.jsyn.unitgen.ChannelIn} - * or {@link com.jsyn.unitgen.ChannelOut}, which can be associated with any indexed channel. - * - * @param frameRate in Hertz - * @param inputDeviceID obtained from an {@link AudioDeviceManager} or pass - * AudioDeviceManager.USE_DEFAULT_DEVICE - * @param numInputChannels 0 for no input, 1 for mono, 2 for stereo, etcetera - * @param ouputDeviceID obtained from an AudioDeviceManager or pass - * AudioDeviceManager.USE_DEFAULT_DEVICE - * @param numOutputChannels 0 for no output, 1 for mono, 2 for stereo, etcetera - */ - public void start(int frameRate, int inputDeviceID, int numInputChannels, int ouputDeviceID, - int numOutputChannels); - - /** @return JSyn version as a string */ - public String getVersion(); - - /** @return version as an integer that always increases */ - public int getVersionCode(); - - /** Stops the background thread that generates the audio. */ - public void stop(); - - /** - * An AudioDeviceManager is an interface to audio hardware. It might be implemented using - * JavaSound or a wrapper around PortAudio. - * - * @return audio device manager being used by the synthesizer. - */ - public AudioDeviceManager getAudioDeviceManager(); - - /** @return the frame rate in samples per second */ - public int getFrameRate(); - - /** - * Add a unit generator to the synthesizer so it can be played. This is required before starting - * or connecting a unit generator into a network. - * - * @param ugen a unit generator to be executed by the synthesizer - */ - public void add(UnitGenerator ugen); - - /** Removes a unit generator added using add(). */ - public void remove(UnitGenerator ugen); - - /** @return the current audio time in seconds */ - public double getCurrentTime(); - - /** - * Start a unit generator at the specified time. This is not needed if a unit generator's output - * is connected to other units. Typically you only need to start units that have no outputs, for - * example LineOut or ChannelOut. - */ - public void startUnit(UnitGenerator unit, double time); - - public void startUnit(UnitGenerator unit, TimeStamp timeStamp); - - /** - * The startUnit and stopUnit methods are mainly for internal use. - * Please call unit.start() or unit.stop() instead. - * @param unit - */ - public void startUnit(UnitGenerator unit); - - public void stopUnit(UnitGenerator unit, double time); - - public void stopUnit(UnitGenerator unit, TimeStamp timeStamp); - - /** - * The startUnit and stopUnit methods are mainly for internal use. - * Please call unit.start() or unit.stop() instead. - * @param unit - */ - public void stopUnit(UnitGenerator unit); - - /** - * Sleep until the specified audio time is reached. In non-real-time mode, this will enable the - * synthesizer to run. - */ - public void sleepUntil(double time) throws InterruptedException; - - /** - * Sleep for the specified audio time duration. In non-real-time mode, this will enable the - * synthesizer to run. - */ - public void sleepFor(double duration) throws InterruptedException; - - /** - * If set true then the synthesizer will generate audio in real-time. Set it true for live - * audio. If false then JSyn will run in non-real-time mode. This can be used to generate audio - * to be written to a file. The default is true. - * - * @param realTime - */ - public void setRealTime(boolean realTime); - - /** Is JSyn running in real-time mode? */ - public boolean isRealTime(); - - /** Create a TimeStamp using the current audio time. */ - public TimeStamp createTimeStamp(); - - /** @return the current CPU usage as a fraction between 0.0 and 1.0 */ - public double getUsage(); - - /** @return inverse of frameRate, to avoid expensive divides */ - public double getFramePeriod(); - - /** - * This count is not reset if you stop and restart. - * - * @return number of frames synthesized - */ - public long getFrameCount(); - - /** Queue a command to be processed at a specific time in the background audio thread. */ - public void scheduleCommand(TimeStamp timeStamp, ScheduledCommand command); - - /** Queue a command to be processed at a specific time in the background audio thread. */ - public void scheduleCommand(double time, ScheduledCommand command); - - /** Queue a command to be processed as soon as possible in the background audio thread. */ - public void queueCommand(ScheduledCommand command); - - /** - * Clear all scheduled commands from the queue. - * Commands will be discarded. - */ - public void clearCommandQueue(); - - /** - * @return true if the Synthesizer has been started - */ - public boolean isRunning(); - - /** - * Add a task that will be run repeatedly on the Audio Thread before it generates every new block of Audio. - * This task must be very quick and should not perform any blocking operations. If you are not - * certain that you need an Audio rate task then don't use this. - * - * @param task - */ - public void addAudioTask(Runnable task); - - public void removeAudioTask(Runnable task); - -} diff --git a/src/com/jsyn/apps/AboutJSyn.java b/src/com/jsyn/apps/AboutJSyn.java deleted file mode 100644 index e6c1fbd..0000000 --- a/src/com/jsyn/apps/AboutJSyn.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright 1997 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.apps; - -import java.awt.GridLayout; - -import javax.swing.JApplet; -import javax.swing.JLabel; -import javax.swing.JPanel; -import javax.swing.SwingConstants; - -import com.jsyn.JSyn; -import com.jsyn.Synthesizer; -import com.jsyn.swing.JAppletFrame; -import com.jsyn.swing.PortControllerFactory; -import com.jsyn.unitgen.LineOut; -import com.jsyn.unitgen.LinearRamp; -import com.jsyn.unitgen.SineOscillator; -import com.jsyn.unitgen.UnitOscillator; - -/** - * Show the version of JSyn and play some sine waves. This program will be run if you double click - * the JSyn jar file. - * - * @author Phil Burk (C) 2011 Mobileer Inc - */ -public class AboutJSyn extends JApplet { - private static final long serialVersionUID = -2704222221111608377L; - private Synthesizer synth; - private UnitOscillator osc1; - private UnitOscillator osc2; - private LinearRamp lag; - private LineOut lineOut; - - @Override - public void init() { - synth = JSyn.createSynthesizer(); - - // Add a tone generator. - synth.add(osc1 = new SineOscillator()); - synth.add(osc2 = new SineOscillator()); - // Add a lag to smooth out amplitude changes and avoid pops. - synth.add(lag = new LinearRamp()); - // Add an output mixer. - synth.add(lineOut = new LineOut()); - // Connect the oscillator to the output. - osc1.output.connect(0, lineOut.input, 0); - osc2.output.connect(0, lineOut.input, 1); - - // Arrange the faders in a stack. - setLayout(new GridLayout(0, 1)); - - JPanel infoPanel = new JPanel(); - infoPanel.setLayout(new GridLayout(0, 1)); - infoPanel.add(new JLabel("About: " + synth, SwingConstants.CENTER)); - infoPanel.add(new JLabel("From: http://www.softsynth.com/", SwingConstants.CENTER)); - infoPanel.add(new JLabel("(C) 1997-2011 Mobileer Inc", SwingConstants.CENTER)); - add(infoPanel); - - // Set the minimum, current and maximum values for the port. - lag.output.connect(osc1.amplitude); - lag.output.connect(osc2.amplitude); - lag.input.setup(0.001, 0.5, 1.0); - lag.time.set(0.1); - lag.input.setName("Amplitude"); - add(PortControllerFactory.createExponentialPortSlider(lag.input)); - - osc1.frequency.setup(50.0, 300.0, 3000.0); - osc1.frequency.setName("Frequency (Left)"); - add(PortControllerFactory.createExponentialPortSlider(osc1.frequency)); - osc2.frequency.setup(50.0, 302.0, 3000.0); - osc2.frequency.setName("Frequency (Right)"); - add(PortControllerFactory.createExponentialPortSlider(osc2.frequency)); - validate(); - } - - @Override - public void start() { - // Start synthesizer using default stereo output at 44100 Hz. - synth.start(); - // We only need to start the LineOut. It will pull data from the - // oscillator. - lineOut.start(); - } - - @Override - public void stop() { - synth.stop(); - } - - /* Can be run as either an application or as an applet. */ - public static void main(String args[]) { - AboutJSyn applet = new AboutJSyn(); - JAppletFrame frame = new JAppletFrame("About JSyn", applet); - frame.setSize(440, 300); - frame.setVisible(true); - frame.test(); - } - -} diff --git a/src/com/jsyn/apps/InstrumentTester.java b/src/com/jsyn/apps/InstrumentTester.java deleted file mode 100644 index 6e347cd..0000000 --- a/src/com/jsyn/apps/InstrumentTester.java +++ /dev/null @@ -1,205 +0,0 @@ -/* - * Copyright 2012 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.apps; - -import java.awt.BorderLayout; -import java.io.IOException; - -import javax.sound.midi.MidiDevice; -import javax.sound.midi.MidiMessage; -import javax.sound.midi.MidiUnavailableException; -import javax.sound.midi.Receiver; -import javax.swing.JApplet; - -import com.jsyn.JSyn; -import com.jsyn.Synthesizer; -import com.jsyn.devices.javasound.MidiDeviceTools; -import com.jsyn.instruments.JSynInstrumentLibrary; -import com.jsyn.midi.MessageParser; -import com.jsyn.swing.InstrumentBrowser; -import com.jsyn.swing.JAppletFrame; -import com.jsyn.swing.PresetSelectionListener; -import com.jsyn.swing.SoundTweaker; -import com.jsyn.unitgen.LineOut; -import com.jsyn.unitgen.UnitSource; -import com.jsyn.unitgen.UnitVoice; -import com.jsyn.util.PolyphonicInstrument; -import com.jsyn.util.VoiceDescription; -import com.softsynth.math.AudioMath; -import com.softsynth.shared.time.TimeStamp; - -/** - * Let the user select an instrument using the InstrumentBrowser and play - * them using the ASCII keyboard or with MIDI. - * Sound parameters can be tweaked using faders. - * - * @author Phil Burk (C) 2012 Mobileer Inc - */ -public class InstrumentTester extends JApplet { - private static final long serialVersionUID = -2704222221111608377L; - private Synthesizer synth; - private LineOut lineOut; - private SoundTweaker tweaker; - protected PolyphonicInstrument instrument; - private MyParser messageParser; - - class MyParser extends MessageParser { - - @Override - public void controlChange(int channel, int index, int value) { - } - - @Override - public void noteOff(int channel, int noteNumber, int velocity) { - instrument.noteOff(noteNumber, synth.createTimeStamp()); - } - - @Override - public void noteOn(int channel, int noteNumber, int velocity) { - double frequency = AudioMath.pitchToFrequency(noteNumber); - double amplitude = velocity / (4 * 128.0); - TimeStamp timeStamp = synth.createTimeStamp(); - instrument.noteOn(noteNumber, frequency, amplitude, timeStamp); - } - - } - - // Write a Receiver to get the messages from a Transmitter. - class CustomReceiver implements Receiver { - @Override - public void close() { - System.out.print("Closed."); - } - - @Override - public void send(MidiMessage message, long timeStamp) { - byte[] bytes = message.getMessage(); - messageParser.parse(bytes); - } - } - - public int setupMidiKeyboard() throws MidiUnavailableException, IOException, InterruptedException { - messageParser = new MyParser(); - - int result = 2; - MidiDevice keyboard = MidiDeviceTools.findKeyboard(); - Receiver receiver = new CustomReceiver(); - // Just use default synthesizer. - if (keyboard != null) { - // If you forget to open them you will hear no sound. - keyboard.open(); - // Put the receiver in the transmitter. - // This gives fairly low latency playing. - keyboard.getTransmitter().setReceiver(receiver); - System.out.println("Play MIDI keyboard: " + keyboard.getDeviceInfo().getDescription()); - result = 0; - } else { - System.out.println("Could not find a keyboard."); - } - return result; - } - - @Override - public void init() { - setLayout(new BorderLayout()); - - synth = JSyn.createSynthesizer(); - synth.add(lineOut = new LineOut()); - - InstrumentBrowser browser = new InstrumentBrowser(new JSynInstrumentLibrary()); - browser.addPresetSelectionListener(new PresetSelectionListener() { - - @Override - public void presetSelected(VoiceDescription voiceDescription, int presetIndex) { - UnitVoice[] voices = new UnitVoice[8]; - for (int i = 0; i < voices.length; i++) { - voices[i] = voiceDescription.createUnitVoice(); - } - instrument = new PolyphonicInstrument(voices); - synth.add(instrument); - instrument.usePreset(presetIndex, synth.createTimeStamp()); - String title = voiceDescription.getVoiceClassName() + ": " - + voiceDescription.getPresetNames()[presetIndex]; - useSource(instrument, title); - } - }); - add(browser, BorderLayout.NORTH); - - try { - setupMidiKeyboard(); - } catch (MidiUnavailableException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (IOException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (InterruptedException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - - validate(); - } - - private void useSource(UnitSource voice, String title) { - - lineOut.input.disconnectAll(0); - lineOut.input.disconnectAll(1); - - // Connect the source to both left and right output. - voice.getOutput().connect(0, lineOut.input, 0); - voice.getOutput().connect(0, lineOut.input, 1); - - if (tweaker != null) { - remove(tweaker); - } - try { - if (synth.isRunning()) { - synth.sleepFor(0.1); - } - } catch (InterruptedException e) { - e.printStackTrace(); - } - tweaker = new SoundTweaker(synth, title, voice); - add(tweaker, BorderLayout.CENTER); - validate(); - } - - @Override - public void start() { - // Start synthesizer using default stereo output at 44100 Hz. - synth.start(); - // We only need to start the LineOut. It will pull data from the - // oscillator. - lineOut.start(); - } - - @Override - public void stop() { - synth.stop(); - } - - /* Can be run as either an application or as an applet. */ - public static void main(String args[]) { - InstrumentTester applet = new InstrumentTester(); - JAppletFrame frame = new JAppletFrame("InstrumentTester", applet); - frame.setSize(600, 800); - frame.setVisible(true); - frame.test(); - } - -} diff --git a/src/com/jsyn/data/AudioSample.java b/src/com/jsyn/data/AudioSample.java deleted file mode 100644 index dcbbae5..0000000 --- a/src/com/jsyn/data/AudioSample.java +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright 2010 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.data; - -import java.util.ArrayList; - -/** - * Base class for FloatSample and ShortSample. - * - * @author Phil Burk (C) 2010 Mobileer Inc - */ -public abstract class AudioSample extends SequentialDataCommon { - protected int numFrames; - protected int channelsPerFrame = 1; - private double frameRate = 44100.0; - private double pitch; - private ArrayList markers; - - public abstract void allocate(int numFrames, int channelsPerFrame); - - @Override - public double getRateScaler(int index, double synthesisRate) { - return 1.0; - } - - public double getFrameRate() { - return frameRate; - } - - public void setFrameRate(double f) { - this.frameRate = f; - } - - @Override - public int getNumFrames() { - return numFrames; - } - - @Override - public int getChannelsPerFrame() { - return channelsPerFrame; - } - - public void setChannelsPerFrame(int channelsPerFrame) { - this.channelsPerFrame = channelsPerFrame; - } - - /** - * Set the recorded pitch as a fractional MIDI semitone value where 60 is Middle C. - * - * @param pitch - */ - public void setPitch(double pitch) { - this.pitch = pitch; - } - - public double getPitch() { - return pitch; - } - - public int getMarkerCount() { - if (markers == null) - return 0; - else - return markers.size(); - } - - public SampleMarker getMarker(int index) { - if (markers == null) - return null; - else - return markers.get(index); - } - - /** - * Add a marker that will be stored sorted by position. This is normally used internally by the - * SampleLoader. - * - * @param marker - */ - public void addMarker(SampleMarker marker) { - if (markers == null) - markers = new ArrayList(); - int idx = markers.size(); - for (int k = 0; k < markers.size(); k++) { - SampleMarker cue = markers.get(k); - if (cue.position > marker.position) { - idx = k; - break; - } - } - markers.add(idx, marker); - } -} diff --git a/src/com/jsyn/data/DoubleTable.java b/src/com/jsyn/data/DoubleTable.java deleted file mode 100644 index ca64c94..0000000 --- a/src/com/jsyn/data/DoubleTable.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright 2010 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.data; - -import com.jsyn.exceptions.ChannelMismatchException; - -/** - * Evaluate a Function by interpolating between the values in a table. This can be used for - * wavetable lookup or waveshaping. - * - * @author Phil Burk (C) 2010 Mobileer Inc - */ -public class DoubleTable implements Function { - private double[] table; - - public DoubleTable(int numFrames) { - allocate(numFrames); - } - - public DoubleTable(double[] data) { - allocate(data.length); - write(data); - } - - public DoubleTable(ShortSample shortSample) { - if (shortSample.getChannelsPerFrame() != 1) { - throw new ChannelMismatchException("DoubleTable can only be built from mono samples."); - } - short[] buffer = new short[256]; - int framesLeft = shortSample.getNumFrames(); - allocate(framesLeft); - int cursor = 0; - while (framesLeft > 0) { - int numTransfer = framesLeft; - if (numTransfer > buffer.length) { - numTransfer = buffer.length; - } - shortSample.read(cursor, buffer, 0, numTransfer); - write(cursor, buffer, 0, numTransfer); - cursor += numTransfer; - framesLeft -= numTransfer; - } - } - - public void allocate(int numFrames) { - table = new double[numFrames]; - } - - public int length() { - return table.length; - } - - public void write(double[] data) { - write(0, data, 0, data.length); - } - - public void write(int startFrame, short[] data, int startIndex, int numFrames) { - for (int i = 0; i < numFrames; i++) { - table[startFrame + i] = data[startIndex + i] * (1.0 / 32768.0); - } - } - - public void write(int startFrame, double[] data, int startIndex, int numFrames) { - for (int i = 0; i < numFrames; i++) { - table[startFrame + i] = data[startIndex + i]; - } - } - - /** - * Treat the double array as a lookup table with a domain of -1.0 to 1.0. If the input is out of - * range then the output will clip to the end values. - * - * @param input - * @return interpolated value from table - */ - @Override - public double evaluate(double input) { - double interp; - if (input < -1.0) { - interp = table[0]; - } else if (input < 1.0) { - double fractionalIndex = (table.length - 1) * (input - (-1.0)) / 2.0; - // We don't need floor() because fractionalIndex >= 0.0 - int index = (int) fractionalIndex; - double fraction = fractionalIndex - index; - - double s1 = table[index]; - double s2 = table[index + 1]; - interp = ((s2 - s1) * fraction) + s1; - } else { - interp = table[table.length - 1]; - } - return interp; - } -} diff --git a/src/com/jsyn/data/FloatSample.java b/src/com/jsyn/data/FloatSample.java deleted file mode 100644 index 2d8c973..0000000 --- a/src/com/jsyn/data/FloatSample.java +++ /dev/null @@ -1,164 +0,0 @@ -/* - * Copyright 2010 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.data; - -import com.jsyn.unitgen.FixedRateMonoReader; -import com.jsyn.unitgen.FixedRateStereoReader; -import com.jsyn.unitgen.VariableRateMonoReader; -import com.jsyn.unitgen.VariableRateStereoReader; -import com.jsyn.util.SampleLoader; - -/** - * Store multi-channel floating point audio data in an interleaved buffer. The values are stored as - * 32-bit floats. You can play samples using one of the readers, for example VariableRateMonoReader. - * - * @author Phil Burk (C) 2010 Mobileer Inc - * @see SampleLoader - * @see FixedRateMonoReader - * @see FixedRateStereoReader - * @see VariableRateMonoReader - * @see VariableRateStereoReader - */ -public class FloatSample extends AudioSample implements Function { - private float[] buffer; - - public FloatSample() { - } - - /** Constructor for mono samples. */ - public FloatSample(int numFrames) { - this(numFrames, 1); - } - - /** Constructor for mono samples with data. */ - public FloatSample(float[] data) { - this(data.length, 1); - write(data); - } - - /** Constructor for multi-channel samples with data. */ - public FloatSample(float[] data, int channelsPerFrame) { - this(data.length / channelsPerFrame, channelsPerFrame); - write(data); - } - - /** - * Create a silent sample with enough memory to hold the audio data. The number of sample - * numbers in the array will be numFrames*channelsPerFrame. - * - * @param numFrames number of sample groups. A stereo frame contains 2 samples. - * @param channelsPerFrame 1 for mono, 2 for stereo - */ - public FloatSample(int numFrames, int channelsPerFrame) { - allocate(numFrames, channelsPerFrame); - } - - /** - * Allocate memory to hold the audio data. The number of sample numbers in the array will be - * numFrames*channelsPerFrame. - * - * @param numFrames number of sample groups. A stereo frame contains 2 samples. - * @param channelsPerFrame 1 for mono, 2 for stereo - */ - @Override - public void allocate(int numFrames, int channelsPerFrame) { - buffer = new float[numFrames * channelsPerFrame]; - this.numFrames = numFrames; - this.channelsPerFrame = channelsPerFrame; - } - - /** - * Note that in a stereo sample, a frame has two values. - * - * @param startFrame index of frame in the sample - * @param data data to be written - * @param startIndex index of first value in array - * @param numFrames - */ - public void write(int startFrame, float[] data, int startIndex, int numFrames) { - int numSamplesToWrite = numFrames * channelsPerFrame; - int firstSampleIndexToWrite = startFrame * channelsPerFrame; - System.arraycopy(data, startIndex, buffer, firstSampleIndexToWrite, numSamplesToWrite); - } - - /** - * Note that in a stereo sample, a frame has two values. - * - * @param startFrame index of frame in the sample - * @param data array to receive the data from the sample - * @param startIndex index of first location in array to start filling - * @param numFrames - */ - public void read(int startFrame, float[] data, int startIndex, int numFrames) { - int numSamplesToRead = numFrames * channelsPerFrame; - int firstSampleIndexToRead = startFrame * channelsPerFrame; - System.arraycopy(buffer, firstSampleIndexToRead, data, startIndex, numSamplesToRead); - } - - /** - * Write the entire array to the sample. The sample data must have already been allocated with - * enough room to contain the data. - * - * @param data - */ - public void write(float[] data) { - write(0, data, 0, data.length / getChannelsPerFrame()); - } - - public void read(float[] data) { - read(0, data, 0, data.length / getChannelsPerFrame()); - } - - @Override - public double readDouble(int index) { - return buffer[index]; - } - - @Override - public void writeDouble(int index, double value) { - buffer[index] = (float) value; - } - - /** - * Interpolate between two adjacent samples. - * Note that this will only work for mono, single channel samples. - * - * @param fractionalIndex must be >=0 and < (size-1) - */ - public double interpolate(double fractionalIndex) { - int index = (int) fractionalIndex; - float phase = (float) (fractionalIndex - index); - float source = buffer[index]; - float target = buffer[index + 1]; - return ((target - source) * phase) + source; - } - - /** - * Note that this will only work for mono, single channel samples. - */ - @Override - public double evaluate(double input) { - // Input ranges from -1 to +1 - // Map it to range of sample with guard point. - double normalizedInput = (input + 1.0) * 0.5; - // Clip so it does not go out of range of the sample. - if (normalizedInput < 0.0) normalizedInput = 0.0; - else if (normalizedInput > 1.0) normalizedInput = 1.0; - double fractionalIndex = (getNumFrames() - 1.01) * normalizedInput; - return interpolate(fractionalIndex); - } -} diff --git a/src/com/jsyn/data/Function.java b/src/com/jsyn/data/Function.java deleted file mode 100644 index c0e6566..0000000 --- a/src/com/jsyn/data/Function.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2010 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.data; - -import com.jsyn.unitgen.FunctionEvaluator; -import com.jsyn.unitgen.FunctionOscillator; - -/** - * @author Phil Burk (C) 2010 Mobileer Inc - * @see FunctionEvaluator - * @see FunctionOscillator - */ -public interface Function { - /** - * Convert an input value to an output value. - * - * @param input - * @return generated value - */ - public double evaluate(double input); -} diff --git a/src/com/jsyn/data/HammingWindow.java b/src/com/jsyn/data/HammingWindow.java deleted file mode 100644 index d8e1238..0000000 --- a/src/com/jsyn/data/HammingWindow.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * 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.data; - -public class HammingWindow implements SpectralWindow { - private double[] data; - - /** Construct a generalized Hamming Window */ - public HammingWindow(int length, double alpha, double beta) { - data = new double[length]; - double scaler = 2.0 * Math.PI / (length - 1); - for (int i = 0; i < length; i++) { - data[i] = alpha - (beta * (Math.cos(i * scaler))); - } - } - - /** Traditional Hamming Window with alpha = 25/46 and beta = 21/46 */ - public HammingWindow(int length) { - this(length, 25.0 / 46.0, 21.0 / 46.0); - } - - @Override - public double get(int index) { - return data[index]; - } - -} diff --git a/src/com/jsyn/data/HannWindow.java b/src/com/jsyn/data/HannWindow.java deleted file mode 100644 index 878d07c..0000000 --- a/src/com/jsyn/data/HannWindow.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2013 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.data; - -import com.jsyn.unitgen.SpectralFFT; -import com.jsyn.unitgen.SpectralIFFT; - -/** - * A HammingWindow with alpha and beta = 0.5. - * - * @author Phil Burk (C) 2013 Mobileer Inc - * @see SpectralWindow - * @see SpectralFFT - * @see SpectralIFFT - */ -public class HannWindow extends HammingWindow { - - public HannWindow(int length) { - super(length, 0.5, 0.5); - } - -} diff --git a/src/com/jsyn/data/SampleMarker.java b/src/com/jsyn/data/SampleMarker.java deleted file mode 100644 index d3db1d4..0000000 --- a/src/com/jsyn/data/SampleMarker.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2012 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.data; - -/** - * A marker for an audio sample. - * - * @author (C) 2012 Phil Burk, Mobileer Inc - */ - -public class SampleMarker { - /** Sample frame index. */ - public int position; - public String name; - public String comment; -} diff --git a/src/com/jsyn/data/SegmentedEnvelope.java b/src/com/jsyn/data/SegmentedEnvelope.java deleted file mode 100644 index efdfd89..0000000 --- a/src/com/jsyn/data/SegmentedEnvelope.java +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright 2010 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.data; - -import com.jsyn.unitgen.EnvelopeDAHDSR; -import com.jsyn.unitgen.VariableRateMonoReader; - -/** - * Store an envelope as a series of line segments. Each line is described as a duration and a target - * value. The envelope can be played using a {@link VariableRateMonoReader}. Here is an example that - * generates an envelope that looks like a traditional ADSR envelope. - * - *

- * 
- * 	// Create an amplitude envelope and fill it with data.
- * 	double[] ampData = {
- * 		0.02, 0.9, // duration,value pair 0, "attack"
- * 		0.10, 0.5, // pair 1, "decay"
- * 		0.50, 0.0  // pair 2, "release"
- * 	};
- * 	SegmentedEnvelope ampEnvelope = new SegmentedEnvelope( ampData );
- * 	
- * 	// Hang at end of decay segment to provide a "sustain" segment.
- * 	ampEnvelope.setSustainBegin( 1 );
- * 	ampEnvelope.setSustainEnd( 1 );
- * 	
- * 	// Play the envelope using queueOn so that it uses the sustain and release information.
- * 	synth.add( ampEnv = new VariableRateMonoReader() );
- * 	ampEnv.dataQueue.queueOn( ampEnvelope );
- * 
- * 
- * - * As an alternative you could use an {@link EnvelopeDAHDSR}. - * - * @author Phil Burk (C) 2010 Mobileer Inc - * @see VariableRateMonoReader - * @see EnvelopeDAHDSR - */ -public class SegmentedEnvelope extends SequentialDataCommon { - private double[] buffer; - - public SegmentedEnvelope(int maxFrames) { - allocate(maxFrames); - } - - public SegmentedEnvelope(double[] pairs) { - this(pairs.length / 2); - write(pairs); - } - - public void allocate(int maxFrames) { - buffer = new double[maxFrames * 2]; - this.maxFrames = maxFrames; - this.numFrames = 0; - } - - /** - * Write frames of envelope data. A frame consists of a duration and a value. - * - * @param startFrame Index of frame in envelope to write to. - * @param data Pairs of duration and value. - * @param startIndex Index of frame in data[] to read from. - * @param numToWrite Number of frames (pairs) to write. - */ - public void write(int startFrame, double[] data, int startIndex, int numToWrite) { - System.arraycopy(data, startIndex * 2, buffer, startFrame * 2, numToWrite * 2); - if ((startFrame + numToWrite) > numFrames) { - numFrames = startFrame + numToWrite; - } - } - - public void read(int startFrame, double[] data, int startIndex, int numToRead) { - System.arraycopy(buffer, startFrame * 2, data, startIndex * 2, numToRead * 2); - } - - public void write(double[] data) { - write(0, data, 0, data.length / 2); - } - - public void read(double[] data) { - read(0, data, 0, data.length / 2); - } - - /** Read the value of an envelope, not the duration. */ - @Override - public double readDouble(int index) { - return buffer[(index * 2) + 1]; - } - - @Override - public void writeDouble(int index, double value) { - buffer[(index * 2) + 1] = value; - if ((index + 1) > numFrames) { - numFrames = index + 1; - } - } - - @Override - public double getRateScaler(int index, double synthesisPeriod) { - double duration = buffer[index * 2]; - if (duration < synthesisPeriod) { - duration = synthesisPeriod; - } - return 1.0 / duration; - } - - @Override - public int getChannelsPerFrame() { - return 1; - } -} diff --git a/src/com/jsyn/data/SequentialData.java b/src/com/jsyn/data/SequentialData.java deleted file mode 100644 index 0deb5c9..0000000 --- a/src/com/jsyn/data/SequentialData.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright 2010 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.data; - -import com.jsyn.unitgen.FixedRateMonoReader; -import com.jsyn.unitgen.FixedRateMonoWriter; -import com.jsyn.unitgen.FixedRateStereoReader; -import com.jsyn.unitgen.FixedRateStereoWriter; -import com.jsyn.unitgen.VariableRateMonoReader; -import com.jsyn.unitgen.VariableRateStereoReader; - -/** - * Interface for objects that can be read and/or written by index. The index is not stored - * internally so they can be shared by multiple readers. - * - * @author Phil Burk (C) 2010 Mobileer Inc - * @see FixedRateMonoReader - * @see FixedRateStereoReader - * @see FixedRateMonoWriter - * @see FixedRateStereoWriter - * @see VariableRateMonoReader - * @see VariableRateStereoReader - */ -public interface SequentialData { - /** - * Write a value at the given index. - * - * @param index sample index is ((frameIndex * channelsPerFrame) + channelIndex) - * @param value the value to be written - */ - void writeDouble(int index, double value); - - /** - * Read a value from the sample independently from the internal storage format. - * - * @param index sample index is ((frameIndex * channelsPerFrame) + channelIndex) - */ - - double readDouble(int index); - - /*** - * @return Beginning of sustain loop or -1 if no loop. - */ - public int getSustainBegin(); - - /** - * SustainEnd value is the frame index of the frame just past the end of the loop. The number of - * frames included in the loop is (SustainEnd - SustainBegin). - * - * @return End of sustain loop or -1 if no loop. - */ - public int getSustainEnd(); - - /*** - * @return Beginning of release loop or -1 if no loop. - */ - public int getReleaseBegin(); - - /*** - * @return End of release loop or -1 if no loop. - */ - public int getReleaseEnd(); - - /** - * Get rate to play the data. In an envelope this correspond to the inverse of the frame - * duration and would vary frame to frame. For an audio sample it is 1.0. - * - * @param index - * @param synthesisRate - * @return rate to scale the playback speed. - */ - double getRateScaler(int index, double synthesisRate); - - /** - * @return For a stereo sample, return 2. - */ - int getChannelsPerFrame(); - - /** - * @return The number of valid frames that can be read. - */ - int getNumFrames(); -} diff --git a/src/com/jsyn/data/SequentialDataCommon.java b/src/com/jsyn/data/SequentialDataCommon.java deleted file mode 100644 index 5cc51df..0000000 --- a/src/com/jsyn/data/SequentialDataCommon.java +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright 2010 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.data; - -/** - * Abstract base class for envelopes and samples that adds sustain and release loops. - * - * @author Phil Burk (C) 2010 Mobileer Inc - */ -public abstract class SequentialDataCommon implements SequentialData { - protected int numFrames; - protected int maxFrames; - private int sustainBegin = -1; - private int sustainEnd = -1; - private int releaseBegin = -1; - private int releaseEnd = -1; - - @Override - public abstract void writeDouble(int index, double value); - - @Override - public abstract double readDouble(int index); - - @Override - public abstract double getRateScaler(int index, double synthesisRate); - - @Override - public abstract int getChannelsPerFrame(); - - /** - * @return Maximum number of frames of data. - */ - public int getMaxFrames() { - return maxFrames; - } - - /** - * Set number of frames of data. Input will be clipped to maxFrames. This is useful when - * changing the contents of a sample or envelope. - */ - public void setNumFrames(int numFrames) { - if (numFrames > maxFrames) - numFrames = maxFrames; - this.numFrames = numFrames; - } - - @Override - public int getNumFrames() { - return numFrames; - } - - // JavaDocs will be copied from SequentialData - - @Override - public int getSustainBegin() { - return this.sustainBegin; - } - - @Override - public int getSustainEnd() { - return this.sustainEnd; - } - - @Override - public int getReleaseBegin() { - return this.releaseBegin; - } - - @Override - public int getReleaseEnd() { - return this.releaseEnd; - } - - /** - * Set beginning of a sustain loop. When UnitDataQueuePort.queueOn() is called, - * if the loop is set then the attack portion will be queued followed by this sustain - * region using queueLoop(). - * The number of frames in the loop will be (SustainEnd - SustainBegin). - *

- * For a steady sustain level, like in an ADSR envelope, set SustainBegin and - * SustainEnd to the same frame. - *

- * For a sustain that is modulated, include two or more frames in the loop. - * - * @param sustainBegin - */ - public void setSustainBegin(int sustainBegin) { - this.sustainBegin = sustainBegin; - } - - /** - * SustainEnd value is the frame index of the frame just past the end of the loop. - * The number of frames included in the loop is (SustainEnd - SustainBegin). - * - * @param sustainEnd - */ - public void setSustainEnd(int sustainEnd) { - this.sustainEnd = sustainEnd; - } - - /** - * The release loop behaves like the sustain loop but it is triggered - * by UnitDataQueuePort.queueOff(). - * - * @param releaseBegin - */ - public void setReleaseBegin(int releaseBegin) { - this.releaseBegin = releaseBegin; - } - - /** - * ReleaseEnd value is the frame index of the frame just past the end of the loop. - * The number of frames included in the loop is (ReleaseEnd - ReleaseBegin). - * - * @param releaseEnd - */ - - public void setReleaseEnd(int releaseEnd) { - this.releaseEnd = releaseEnd; - } - -} diff --git a/src/com/jsyn/data/ShortSample.java b/src/com/jsyn/data/ShortSample.java deleted file mode 100644 index 4a4110e..0000000 --- a/src/com/jsyn/data/ShortSample.java +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright 2010 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.data; - -import com.jsyn.engine.SynthesisEngine; -import com.jsyn.unitgen.FixedRateMonoReader; -import com.jsyn.unitgen.FixedRateStereoReader; -import com.jsyn.unitgen.VariableRateMonoReader; -import com.jsyn.unitgen.VariableRateStereoReader; -import com.jsyn.util.SampleLoader; - -/** - * Store multi-channel short audio data in an interleaved buffer. - * - * @author Phil Burk (C) 2010 Mobileer Inc - * @see SampleLoader - * @see FixedRateMonoReader - * @see FixedRateStereoReader - * @see VariableRateMonoReader - * @see VariableRateStereoReader - */ -public class ShortSample extends AudioSample { - private short[] buffer; - - public ShortSample() { - } - - public ShortSample(int numFrames, int channelsPerFrame) { - allocate(numFrames, channelsPerFrame); - } - - /** Constructor for mono samples with data. */ - public ShortSample(short[] data) { - this(data.length, 1); - write(data); - } - - /** Constructor for multi-channel samples with data. */ - public ShortSample(short[] data, int channelsPerFrame) { - this(data.length / channelsPerFrame, channelsPerFrame); - write(data); - } - - @Override - public void allocate(int numFrames, int channelsPerFrame) { - buffer = new short[numFrames * channelsPerFrame]; - this.numFrames = numFrames; - this.channelsPerFrame = channelsPerFrame; - } - - /** - * Note that in a stereo sample, a frame has two values. - * - * @param startFrame index of frame in the sample - * @param data data to be written - * @param startIndex index of first value in array - * @param numFrames - */ - public void write(int startFrame, short[] data, int startIndex, int numFrames) { - int numSamplesToWrite = numFrames * channelsPerFrame; - int firstSampleIndexToWrite = startFrame * channelsPerFrame; - System.arraycopy(data, startIndex, buffer, firstSampleIndexToWrite, numSamplesToWrite); - } - - /** - * Note that in a stereo sample, a frame has two values. - * - * @param startFrame index of frame in the sample - * @param data array to receive the data from the sample - * @param startIndex index of first location in array to start filling - * @param numFrames - */ - public void read(int startFrame, short[] data, int startIndex, int numFrames) { - int numSamplesToRead = numFrames * channelsPerFrame; - int firstSampleIndexToRead = startFrame * channelsPerFrame; - System.arraycopy(buffer, firstSampleIndexToRead, data, startIndex, numSamplesToRead); - } - - public void write(short[] data) { - write(0, data, 0, data.length); - } - - public void read(short[] data) { - read(0, data, 0, data.length); - } - - public short readShort(int index) { - return buffer[index]; - } - - public void writeShort(int index, short value) { - buffer[index] = value; - } - - /** Read a sample converted to a double in the range -1.0 to almost 1.0. */ - @Override - public double readDouble(int index) { - return SynthesisEngine.convertShortToDouble(buffer[index]); - } - - /** - * Write a double that will be clipped to the range -1.0 to almost 1.0 and converted to a short. - */ - @Override - public void writeDouble(int index, double value) { - buffer[index] = SynthesisEngine.convertDoubleToShort(value); - } - -} diff --git a/src/com/jsyn/data/SpectralWindow.java b/src/com/jsyn/data/SpectralWindow.java deleted file mode 100644 index 0fcfac4..0000000 --- a/src/com/jsyn/data/SpectralWindow.java +++ /dev/null @@ -1,21 +0,0 @@ -/* - * 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.data; - -public interface SpectralWindow { - public double get(int index); -} diff --git a/src/com/jsyn/data/SpectralWindowFactory.java b/src/com/jsyn/data/SpectralWindowFactory.java deleted file mode 100644 index 01cced6..0000000 --- a/src/com/jsyn/data/SpectralWindowFactory.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2013 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.data; - -import com.jsyn.unitgen.SpectralFFT; -import com.jsyn.unitgen.SpectralFilter; -import com.jsyn.unitgen.SpectralIFFT; - -/** - * Create shared windows as needed for use with FFTs and IFFTs. - * - * @author Phil Burk (C) 2013 Mobileer Inc - * @see SpectralWindow - * @see SpectralFFT - * @see SpectralIFFT - * @see SpectralFilter - */ -public class SpectralWindowFactory { - private static final int NUM_WINDOWS = 16; - private static final int MIN_SIZE_LOG_2 = 2; - private static HammingWindow[] hammingWindows = new HammingWindow[NUM_WINDOWS]; - private static HannWindow[] hannWindows = new HannWindow[NUM_WINDOWS]; - - /** @return a shared standard HammingWindow */ - public static HammingWindow getHammingWindow(int sizeLog2) { - int index = sizeLog2 - MIN_SIZE_LOG_2; - if (hammingWindows[index] == null) { - hammingWindows[index] = new HammingWindow(1 << sizeLog2); - } - return hammingWindows[index]; - } - - /** @return a shared HannWindow */ - public static HannWindow getHannWindow(int sizeLog2) { - int index = sizeLog2 - MIN_SIZE_LOG_2; - if (hannWindows[index] == null) { - hannWindows[index] = new HannWindow(1 << sizeLog2); - } - return hannWindows[index]; - } -} diff --git a/src/com/jsyn/data/Spectrum.java b/src/com/jsyn/data/Spectrum.java deleted file mode 100644 index 66e4ee4..0000000 --- a/src/com/jsyn/data/Spectrum.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright 2013 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.data; - -import com.jsyn.unitgen.SpectralFFT; -import com.jsyn.unitgen.SpectralIFFT; -import com.jsyn.unitgen.SpectralProcessor; - -/** - * Complex spectrum with real and imaginary parts. The frequency associated with each bin of the - * spectrum is: - * - *

- * frequency = binIndex * sampleRate / size
- * 
- * - * Note that the upper half of the spectrum is above the Nyquist frequency. Those frequencies are - * mirrored around the Nyquist frequency. Note that this spectral API is experimental and may change - * at any time. - * - * @author Phil Burk (C) 2013 Mobileer Inc - * @version 016 - * @see SpectralFFT - * @see SpectralIFFT - * @see SpectralProcessor - */ -public class Spectrum { - private double[] real; - private double[] imaginary; - public static final int DEFAULT_SIZE_LOG_2 = 9; - public static final int DEFAULT_SIZE = 1 << DEFAULT_SIZE_LOG_2; - - public Spectrum() { - this(DEFAULT_SIZE); - } - - public Spectrum(int size) { - setSize(size); - } - - public double[] getReal() { - return real; - } - - public double[] getImaginary() { - return imaginary; - } - - /** - * If you change the size of the spectrum then the real and imaginary arrays will be - * reallocated. - * - * @param size - */ - public void setSize(int size) { - if ((real == null) || (real.length != size)) { - real = new double[size]; - imaginary = new double[size]; - } - } - - public int size() { - return real.length; - } - - /** - * Copy this spectrum to another spectrum of the same length. - * - * @param destination - */ - public void copyTo(Spectrum destination) { - assert (size() == destination.size()); - System.arraycopy(real, 0, destination.real, 0, real.length); - System.arraycopy(imaginary, 0, destination.imaginary, 0, imaginary.length); - } - - public void clear() { - for (int i = 0; i < real.length; i++) { - real[i] = 0.0; - imaginary[i] = 0.0; - } - } -} diff --git a/src/com/jsyn/devices/AudioDeviceFactory.java b/src/com/jsyn/devices/AudioDeviceFactory.java deleted file mode 100644 index 612c81d..0000000 --- a/src/com/jsyn/devices/AudioDeviceFactory.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright 2010 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.devices; - -import com.jsyn.util.JavaTools; - -/** - * Create a device appropriate for the platform. - * - * @author Phil Burk (C) 2010 Mobileer Inc - */ -public class AudioDeviceFactory { - private static AudioDeviceManager instance; - - /** - * Use a custom device interface. Overrides the selection of a default device manager. - * - * @param instance - */ - public static void setInstance(AudioDeviceManager instance) { - AudioDeviceFactory.instance = instance; - } - - /** - * Try to load JPortAudio or JavaSound devices. - * - * @return A device supported on this platform. - */ - public static AudioDeviceManager createAudioDeviceManager() { - return createAudioDeviceManager(false); - } - - /** - * Try to load JPortAudio or JavaSound devices. - * - * @param preferJavaSound if true then try to create a JavaSound manager before other types. - * @return A device supported on this platform. - */ - public static AudioDeviceManager createAudioDeviceManager(boolean preferJavaSound) { - if (preferJavaSound) { - tryJavaSound(); - tryJPortAudio(); - } else { - tryJPortAudio(); - tryJavaSound(); - } - return instance; - } - - private static void tryJavaSound() { - if (instance == null) { - try { - @SuppressWarnings("unchecked") - Class clazz = JavaTools.loadClass( - "com.jsyn.devices.javasound.JavaSoundAudioDevice", false); - if (clazz != null) { - instance = clazz.newInstance(); - } - } catch (Throwable e) { - System.err.println("Could not load JavaSound device. " + e); - } - } - } - - private static void tryJPortAudio() { - if (instance == null) { - try { - if (JavaTools.loadClass("com.portaudio.PortAudio", false) != null) { - instance = (AudioDeviceManager) JavaTools.loadClass( - "com.jsyn.devices.jportaudio.JPortAudioDevice").newInstance(); - } - - } catch (Throwable e) { - System.err.println("Could not load JPortAudio device. " + e); - } - } - } - -} diff --git a/src/com/jsyn/devices/AudioDeviceInputStream.java b/src/com/jsyn/devices/AudioDeviceInputStream.java deleted file mode 100644 index a3d1854..0000000 --- a/src/com/jsyn/devices/AudioDeviceInputStream.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * 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.devices; - -import com.jsyn.io.AudioInputStream; - -public interface AudioDeviceInputStream extends AudioInputStream { - /** Start the input device. */ - public void start(); - - public void stop(); - - /** - * @return Estimated latency in seconds. - */ - public double getLatency(); -} diff --git a/src/com/jsyn/devices/AudioDeviceManager.java b/src/com/jsyn/devices/AudioDeviceManager.java deleted file mode 100644 index ac8d47c..0000000 --- a/src/com/jsyn/devices/AudioDeviceManager.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright 2010 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.devices; - -/** - * Interface for an audio system. This may be implemented using JavaSound, or a native device - * wrapper. - * - * @author Phil Burk (C) 2010 Mobileer Inc - */ -public interface AudioDeviceManager { - /** - * Pass this value to the start method to request the default device ID. - */ - public final static int USE_DEFAULT_DEVICE = -1; - - /** - * @return The number of devices available. - */ - public int getDeviceCount(); - - /** - * Get the name of an audio device. - * - * @param deviceID An index between 0 to deviceCount-1. - * @return A name that can be shown to the user. - */ - public String getDeviceName(int deviceID); - - /** - * @return A name of the device manager that can be shown to the user. - */ - public String getName(); - - /** - * The user can generally select a default device using a control panel that is part of the - * operating system. - * - * @return The ID for the input device that the user has selected as the default. - */ - public int getDefaultInputDeviceID(); - - /** - * The user can generally select a default device using a control panel that is part of the - * operating system. - * - * @return The ID for the output device that the user has selected as the default. - */ - public int getDefaultOutputDeviceID(); - - /** - * @param deviceID - * @return The maximum number of channels that the device will support. - */ - public int getMaxInputChannels(int deviceID); - - /** - * @param deviceID An index between 0 to numDevices-1. - * @return The maximum number of channels that the device will support. - */ - public int getMaxOutputChannels(int deviceID); - - /** - * This the lowest latency that the device can support reliably. It should be used for - * applications that require low latency such as live processing of guitar signals. - * - * @param deviceID An index between 0 to numDevices-1. - * @return Latency in seconds. - */ - public double getDefaultLowInputLatency(int deviceID); - - /** - * This the highest latency that the device can support. High latency is recommended for - * applications that are not time critical, such as recording. - * - * @param deviceID An index between 0 to numDevices-1. - * @return Latency in seconds. - */ - public double getDefaultHighInputLatency(int deviceID); - - public double getDefaultLowOutputLatency(int deviceID); - - public double getDefaultHighOutputLatency(int deviceID); - - /** - * Set latency in seconds for the audio device. If set to zero then the DefaultLowLatency value - * for the device will be used. This is just a suggestion that will be used when the - * AudioDeviceInputStream is started. - **/ - public int setSuggestedInputLatency(double latency); - - public int setSuggestedOutputLatency(double latency); - - /** - * Create a stream that can be used internally by JSyn for outputting audio data. Applications - * should not call this directly. - */ - AudioDeviceOutputStream createOutputStream(int deviceID, int frameRate, int numOutputChannels); - - /** - * Create a stream that can be used internally by JSyn for acquiring audio input data. - * Applications should not call this directly. - */ - AudioDeviceInputStream createInputStream(int deviceID, int frameRate, int numInputChannels); - -} diff --git a/src/com/jsyn/devices/AudioDeviceOutputStream.java b/src/com/jsyn/devices/AudioDeviceOutputStream.java deleted file mode 100644 index 5c17efb..0000000 --- a/src/com/jsyn/devices/AudioDeviceOutputStream.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * 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.devices; - -import com.jsyn.io.AudioOutputStream; - -public interface AudioDeviceOutputStream extends AudioOutputStream { - public void start(); - - public void stop(); - - /** - * @return Estimated latency in seconds. - */ - public double getLatency(); -} diff --git a/src/com/jsyn/devices/javasound/JavaSoundAudioDevice.java b/src/com/jsyn/devices/javasound/JavaSoundAudioDevice.java deleted file mode 100644 index 656dc1c..0000000 --- a/src/com/jsyn/devices/javasound/JavaSoundAudioDevice.java +++ /dev/null @@ -1,431 +0,0 @@ -/* - * 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.devices.javasound; - -import java.util.ArrayList; -import java.util.logging.Logger; - -import javax.sound.sampled.AudioFormat; -import javax.sound.sampled.AudioSystem; -import javax.sound.sampled.DataLine; -import javax.sound.sampled.Line; -import javax.sound.sampled.LineUnavailableException; -import javax.sound.sampled.Mixer; -import javax.sound.sampled.SourceDataLine; -import javax.sound.sampled.TargetDataLine; - -import com.jsyn.devices.AudioDeviceInputStream; -import com.jsyn.devices.AudioDeviceManager; -import com.jsyn.devices.AudioDeviceOutputStream; - -/** - * Use JavaSound to access the audio hardware. - * - * @author Phil Burk (C) 2009 Mobileer Inc - */ -public class JavaSoundAudioDevice implements AudioDeviceManager { - private static final int BYTES_PER_SAMPLE = 2; - private static final boolean USE_BIG_ENDIAN = false; - - ArrayList deviceRecords; - private double suggestedOutputLatency = 0.040; - private double suggestedInputLatency = 0.100; - private int defaultInputDeviceID = -1; - private int defaultOutputDeviceID = -1; - - static Logger logger = Logger.getLogger(JavaSoundAudioDevice.class.getName()); - - public JavaSoundAudioDevice() { - String osName = System.getProperty("os.name"); - if (osName.contains("Windows")) { - suggestedOutputLatency = 0.08; - logger.info("JSyn: default output latency set to " - + ((int) (suggestedOutputLatency * 1000)) + " msec for " + osName); - } - deviceRecords = new ArrayList(); - sniffAvailableMixers(); - dumpAvailableMixers(); - } - - private void dumpAvailableMixers() { - for (DeviceInfo deviceInfo : deviceRecords) { - logger.fine("" + deviceInfo); - } - } - - /** - * Build device info and determine default devices. - */ - private void sniffAvailableMixers() { - Mixer.Info[] mixers = AudioSystem.getMixerInfo(); - for (int i = 0; i < mixers.length; i++) { - DeviceInfo deviceInfo = new DeviceInfo(); - - deviceInfo.name = mixers[i].getName(); - Mixer mixer = AudioSystem.getMixer(mixers[i]); - - Line.Info[] lines = mixer.getTargetLineInfo(); - deviceInfo.maxInputs = scanMaxChannels(lines); - // Remember first device that supports input. - if ((defaultInputDeviceID < 0) && (deviceInfo.maxInputs > 0)) { - defaultInputDeviceID = i; - } - - lines = mixer.getSourceLineInfo(); - deviceInfo.maxOutputs = scanMaxChannels(lines); - // Remember first device that supports output. - if ((defaultOutputDeviceID < 0) && (deviceInfo.maxOutputs > 0)) { - defaultOutputDeviceID = i; - } - - deviceRecords.add(deviceInfo); - } - } - - private int scanMaxChannels(Line.Info[] lines) { - int maxChannels = 0; - for (Line.Info line : lines) { - if (line instanceof DataLine.Info) { - int numChannels = scanMaxChannels(((DataLine.Info) line)); - if (numChannels > maxChannels) { - maxChannels = numChannels; - } - } - } - return maxChannels; - } - - private int scanMaxChannels(DataLine.Info info) { - int maxChannels = 0; - for (AudioFormat format : info.getFormats()) { - int numChannels = format.getChannels(); - if (numChannels > maxChannels) { - maxChannels = numChannels; - } - } - return maxChannels; - } - - class DeviceInfo { - String name; - int maxInputs; - int maxOutputs; - - @Override - public String toString() { - return "AudioDevice: " + name + ", max in = " + maxInputs + ", max out = " + maxOutputs; - } - } - - private class JavaSoundStream { - AudioFormat format; - byte[] bytes; - int frameRate; - int deviceID; - int samplesPerFrame; - - public JavaSoundStream(int deviceID, int frameRate, int samplesPerFrame) { - this.deviceID = deviceID; - this.frameRate = frameRate; - this.samplesPerFrame = samplesPerFrame; - format = new AudioFormat(frameRate, 16, samplesPerFrame, true, USE_BIG_ENDIAN); - } - - Line getDataLine(DataLine.Info info) throws LineUnavailableException { - Line dataLine; - if (deviceID >= 0) { - Mixer.Info[] mixers = AudioSystem.getMixerInfo(); - Mixer mixer = AudioSystem.getMixer(mixers[deviceID]); - dataLine = mixer.getLine(info); - } else { - dataLine = AudioSystem.getLine(info); - } - return dataLine; - } - - int calculateBufferSize(double suggestedOutputLatency) { - int numFrames = (int) (suggestedOutputLatency * frameRate); - int numBytes = numFrames * samplesPerFrame * BYTES_PER_SAMPLE; - return numBytes; - } - - } - - private class JavaSoundOutputStream extends JavaSoundStream implements AudioDeviceOutputStream { - SourceDataLine line; - - public JavaSoundOutputStream(int deviceID, int frameRate, int samplesPerFrame) { - super(deviceID, frameRate, samplesPerFrame); - } - - @Override - public void start() { - DataLine.Info info = new DataLine.Info(SourceDataLine.class, format); - if (!AudioSystem.isLineSupported(info)) { - // Handle the error. - logger.severe("JavaSoundOutputStream - not supported." + format); - } else { - try { - line = (SourceDataLine) getDataLine(info); - int bufferSize = calculateBufferSize(suggestedOutputLatency); - line.open(format, bufferSize); - logger.fine("Output buffer size = " + bufferSize + " bytes."); - line.start(); - - } catch (Exception e) { - e.printStackTrace(); - line = null; - } - } - } - - /** Grossly inefficient. Call the array version instead. */ - @Override - public void write(double value) { - double[] buffer = new double[1]; - buffer[0] = value; - write(buffer, 0, 1); - } - - @Override - public void write(double[] buffer) { - write(buffer, 0, buffer.length); - } - - @Override - public void write(double[] buffer, int start, int count) { - // Allocate byte buffer if needed. - if ((bytes == null) || ((bytes.length * 2) < count)) { - bytes = new byte[count * 2]; - } - - // Convert float samples to LittleEndian bytes. - int byteIndex = 0; - for (int i = 0; i < count; i++) { - // Offset before casting so that we can avoid using floor(). - // Also round by adding 0.5 so that very small signals go to zero. - double temp = (32767.0 * buffer[i + start]) + 32768.5; - int sample = ((int) temp) - 32768; - if (sample > Short.MAX_VALUE) { - sample = Short.MAX_VALUE; - } else if (sample < Short.MIN_VALUE) { - sample = Short.MIN_VALUE; - } - bytes[byteIndex++] = (byte) sample; // little end - bytes[byteIndex++] = (byte) (sample >> 8); // big end - } - - line.write(bytes, 0, byteIndex); - } - - @Override - public void stop() { - if (line != null) { - line.stop(); - line.flush(); - line.close(); - line = null; - } else { - new RuntimeException("AudioOutput stop attempted when no line created.") - .printStackTrace(); - } - } - - @Override - public double getLatency() { - if (line == null) { - return 0.0; - } - int numBytes = line.getBufferSize(); - int numFrames = numBytes / (BYTES_PER_SAMPLE * samplesPerFrame); - return ((double) numFrames) / frameRate; - } - - @Override - public void close() { - } - - } - - private class JavaSoundInputStream extends JavaSoundStream implements AudioDeviceInputStream { - TargetDataLine line; - - public JavaSoundInputStream(int deviceID, int frameRate, int samplesPerFrame) { - super(deviceID, frameRate, samplesPerFrame); - } - - @Override - public void start() { - DataLine.Info info = new DataLine.Info(TargetDataLine.class, format); - if (!AudioSystem.isLineSupported(info)) { - // Handle the error. - logger.severe("JavaSoundInputStream - not supported." + format); - } else { - try { - line = (TargetDataLine) getDataLine(info); - int bufferSize = calculateBufferSize(suggestedInputLatency); - line.open(format, bufferSize); - logger.fine("Input buffer size = " + bufferSize + " bytes."); - line.start(); - } catch (Exception e) { - e.printStackTrace(); - line = null; - } - } - } - - @Override - public double read() { - double[] buffer = new double[1]; - read(buffer, 0, 1); - return buffer[0]; - } - - @Override - public int read(double[] buffer) { - return read(buffer, 0, buffer.length); - } - - @Override - public int read(double[] buffer, int start, int count) { - // Allocate byte buffer if needed. - if ((bytes == null) || ((bytes.length * 2) < count)) { - bytes = new byte[count * 2]; - } - int bytesRead = line.read(bytes, 0, bytes.length); - - // Convert BigEndian bytes to float samples - int bi = 0; - for (int i = 0; i < count; i++) { - int sample = bytes[bi++] & 0x00FF; // little end - sample = sample + (bytes[bi++] << 8); // big end - buffer[i + start] = sample * (1.0 / 32767.0); - } - return bytesRead / 4; - } - - @Override - public void stop() { - if (line != null) { - line.drain(); - line.close(); - } else { - new RuntimeException("AudioInput stop attempted when no line created.") - .printStackTrace(); - } - } - - @Override - public double getLatency() { - if (line == null) { - return 0.0; - } - int numBytes = line.getBufferSize(); - int numFrames = numBytes / (BYTES_PER_SAMPLE * samplesPerFrame); - return ((double) numFrames) / frameRate; - } - - @Override - public int available() { - return line.available() / BYTES_PER_SAMPLE; - } - - @Override - public void close() { - } - - } - - @Override - public AudioDeviceOutputStream createOutputStream(int deviceID, int frameRate, - int samplesPerFrame) { - return new JavaSoundOutputStream(deviceID, frameRate, samplesPerFrame); - } - - @Override - public AudioDeviceInputStream createInputStream(int deviceID, int frameRate, int samplesPerFrame) { - return new JavaSoundInputStream(deviceID, frameRate, samplesPerFrame); - } - - @Override - public double getDefaultHighInputLatency(int deviceID) { - return 0.300; - } - - @Override - public double getDefaultHighOutputLatency(int deviceID) { - return 0.300; - } - - @Override - public int getDefaultInputDeviceID() { - return defaultInputDeviceID; - } - - @Override - public int getDefaultOutputDeviceID() { - return defaultOutputDeviceID; - } - - @Override - public double getDefaultLowInputLatency(int deviceID) { - return 0.100; - } - - @Override - public double getDefaultLowOutputLatency(int deviceID) { - return 0.100; - } - - @Override - public int getDeviceCount() { - return deviceRecords.size(); - } - - @Override - public String getDeviceName(int deviceID) { - return deviceRecords.get(deviceID).name; - } - - @Override - public int getMaxInputChannels(int deviceID) { - return deviceRecords.get(deviceID).maxInputs; - } - - @Override - public int getMaxOutputChannels(int deviceID) { - return deviceRecords.get(deviceID).maxOutputs; - } - - @Override - public int setSuggestedOutputLatency(double latency) { - suggestedOutputLatency = latency; - return 0; - } - - @Override - public int setSuggestedInputLatency(double latency) { - suggestedInputLatency = latency; - return 0; - } - - @Override - public String getName() { - return "JavaSound"; - } - -} diff --git a/src/com/jsyn/devices/javasound/MidiDeviceTools.java b/src/com/jsyn/devices/javasound/MidiDeviceTools.java deleted file mode 100644 index 413beca..0000000 --- a/src/com/jsyn/devices/javasound/MidiDeviceTools.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * 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.devices.javasound; - -import javax.sound.midi.MidiDevice; -import javax.sound.midi.MidiSystem; -import javax.sound.midi.MidiUnavailableException; -import javax.sound.midi.Sequencer; -import javax.sound.midi.Synthesizer; - -public class MidiDeviceTools { - /** Print the available MIDI Devices. */ - public static void listDevices() { - // Ask the MidiSystem what is available. - MidiDevice.Info[] infos = MidiSystem.getMidiDeviceInfo(); - // Print info about each device. - for (MidiDevice.Info info : infos) { - System.out.println("MIDI Info: " + info.getDescription() + ", " + info.getName() + ", " - + info.getVendor() + ", " + info.getVersion()); - // Get the device for more information. - try { - MidiDevice device = MidiSystem.getMidiDevice(info); - System.out.println(" Device: " + ", #recv = " + device.getMaxReceivers() - + ", #xmit = " + device.getMaxTransmitters() + ", open = " - + device.isOpen() + ", " + device); - } catch (MidiUnavailableException e) { - e.printStackTrace(); - } - } - } - - /** Find a MIDI transmitter that contains text in the name. */ - public static MidiDevice findKeyboard(String text) { - MidiDevice keyboard = null; - // Ask the MidiSystem what is available. - MidiDevice.Info[] infos = MidiSystem.getMidiDeviceInfo(); - // Print info about each device. - for (MidiDevice.Info info : infos) { - try { - MidiDevice device = MidiSystem.getMidiDevice(info); - // Hardware devices are not Synthesizers or Sequencers. - if (!(device instanceof Synthesizer) && !(device instanceof Sequencer)) { - // Is this a transmitter? - // Might be -1 if unlimited. - if (device.getMaxTransmitters() != 0) { - if ((text == null) - || (info.getDescription().toLowerCase() - .contains(text.toLowerCase()))) { - keyboard = device; - System.out.println("Chose: " + info.getDescription()); - break; - } - } - } - } catch (MidiUnavailableException e) { - e.printStackTrace(); - } - } - return keyboard; - } - - public static MidiDevice findKeyboard() { - return findKeyboard(null); - } - -} diff --git a/src/com/jsyn/devices/jportaudio/JPortAudioDevice.java b/src/com/jsyn/devices/jportaudio/JPortAudioDevice.java deleted file mode 100644 index a8e574a..0000000 --- a/src/com/jsyn/devices/jportaudio/JPortAudioDevice.java +++ /dev/null @@ -1,259 +0,0 @@ -/* - * 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.devices.jportaudio; - -import com.jsyn.devices.AudioDeviceInputStream; -import com.jsyn.devices.AudioDeviceManager; -import com.jsyn.devices.AudioDeviceOutputStream; -import com.portaudio.BlockingStream; -import com.portaudio.DeviceInfo; -import com.portaudio.HostApiInfo; -import com.portaudio.PortAudio; -import com.portaudio.StreamParameters; - -public class JPortAudioDevice implements AudioDeviceManager { - private double suggestedOutputLatency = 0.030; - private double suggestedInputLatency = 0.050; - private static final int FRAMES_PER_BUFFER = 128; - - // static Logger logger = Logger.getLogger( JPortAudioDevice.class.getName() ); - - public JPortAudioDevice() { - PortAudio.initialize(); - } - - @Override - public int getDeviceCount() { - return PortAudio.getDeviceCount(); - } - - @Override - public String getDeviceName(int deviceID) { - DeviceInfo deviceInfo = PortAudio.getDeviceInfo(deviceID); - HostApiInfo hostInfo = PortAudio.getHostApiInfo(deviceInfo.hostApi); - return deviceInfo.name + " - " + hostInfo.name; - } - - @Override - public int getDefaultInputDeviceID() { - return PortAudio.getDefaultInputDevice(); - } - - @Override - public int getDefaultOutputDeviceID() { - return PortAudio.getDefaultOutputDevice(); - } - - @Override - public int getMaxInputChannels(int deviceID) { - if (deviceID < 0) { - deviceID = PortAudio.getDefaultInputDevice(); - } - return PortAudio.getDeviceInfo(deviceID).maxInputChannels; - } - - @Override - public int getMaxOutputChannels(int deviceID) { - if (deviceID < 0) { - deviceID = PortAudio.getDefaultOutputDevice(); - } - return PortAudio.getDeviceInfo(deviceID).maxOutputChannels; - } - - @Override - public double getDefaultLowInputLatency(int deviceID) { - if (deviceID < 0) { - deviceID = PortAudio.getDefaultInputDevice(); - } - return PortAudio.getDeviceInfo(deviceID).defaultLowInputLatency; - } - - @Override - public double getDefaultHighInputLatency(int deviceID) { - if (deviceID < 0) { - deviceID = PortAudio.getDefaultInputDevice(); - } - return PortAudio.getDeviceInfo(deviceID).defaultHighInputLatency; - } - - @Override - public double getDefaultLowOutputLatency(int deviceID) { - if (deviceID < 0) { - deviceID = PortAudio.getDefaultOutputDevice(); - } - return PortAudio.getDeviceInfo(deviceID).defaultLowOutputLatency; - } - - @Override - public double getDefaultHighOutputLatency(int deviceID) { - if (deviceID < 0) { - deviceID = PortAudio.getDefaultOutputDevice(); - } - return PortAudio.getDeviceInfo(deviceID).defaultHighOutputLatency; - } - - @Override - public int setSuggestedOutputLatency(double latency) { - suggestedOutputLatency = latency; - return 0; - } - - @Override - public int setSuggestedInputLatency(double latency) { - suggestedInputLatency = latency; - return 0; - } - - @Override - public AudioDeviceOutputStream createOutputStream(int deviceID, int frameRate, - int samplesPerFrame) { - return new JPAOutputStream(deviceID, frameRate, samplesPerFrame); - } - - @Override - public AudioDeviceInputStream createInputStream(int deviceID, int frameRate, int samplesPerFrame) { - return new JPAInputStream(deviceID, frameRate, samplesPerFrame); - } - - private class JPAStream { - BlockingStream blockingStream; - float[] floatBuffer = null; - int samplesPerFrame; - - public void close() { - blockingStream.close(); - } - - public void start() { - blockingStream.start(); - } - - public void stop() { - blockingStream.stop(); - } - - } - - private class JPAOutputStream extends JPAStream implements AudioDeviceOutputStream { - - private JPAOutputStream(int deviceID, int frameRate, int samplesPerFrame) { - this.samplesPerFrame = samplesPerFrame; - StreamParameters streamParameters = new StreamParameters(); - streamParameters.channelCount = samplesPerFrame; - if (deviceID < 0) { - deviceID = PortAudio.getDefaultOutputDevice(); - } - streamParameters.device = deviceID; - streamParameters.suggestedLatency = suggestedOutputLatency; - int flags = 0; - System.out.println("Audio output on " + getDeviceName(deviceID)); - blockingStream = PortAudio.openStream(null, streamParameters, frameRate, - FRAMES_PER_BUFFER, flags); - } - - /** Grossly inefficient. Call the array version instead. */ - @Override - public void write(double value) { - double[] buffer = new double[1]; - buffer[0] = value; - write(buffer, 0, 1); - } - - @Override - public void write(double[] buffer) { - write(buffer, 0, buffer.length); - } - - @Override - public void write(double[] buffer, int start, int count) { - // Allocate float buffer if needed. - if ((floatBuffer == null) || (floatBuffer.length < count)) { - floatBuffer = new float[count]; - } - for (int i = 0; i < count; i++) { - - floatBuffer[i] = (float) buffer[i + start]; - } - blockingStream.write(floatBuffer, count / samplesPerFrame); - } - - @Override - public double getLatency() { - return blockingStream.getInfo().outputLatency; - } - } - - private class JPAInputStream extends JPAStream implements AudioDeviceInputStream { - private JPAInputStream(int deviceID, int frameRate, int samplesPerFrame) { - this.samplesPerFrame = samplesPerFrame; - StreamParameters streamParameters = new StreamParameters(); - streamParameters.channelCount = samplesPerFrame; - if (deviceID < 0) { - deviceID = PortAudio.getDefaultInputDevice(); - } - streamParameters.device = deviceID; - streamParameters.suggestedLatency = suggestedInputLatency; - int flags = 0; - System.out.println("Audio input from " + getDeviceName(deviceID)); - blockingStream = PortAudio.openStream(streamParameters, null, frameRate, - FRAMES_PER_BUFFER, flags); - } - - @Override - public double read() { - double[] buffer = new double[1]; - read(buffer, 0, 1); - return buffer[0]; - } - - @Override - public int read(double[] buffer) { - return read(buffer, 0, buffer.length); - } - - @Override - public int read(double[] buffer, int start, int count) { - // Allocate float buffer if needed. - if ((floatBuffer == null) || (floatBuffer.length < count)) { - floatBuffer = new float[count]; - } - blockingStream.read(floatBuffer, count / samplesPerFrame); - - for (int i = 0; i < count; i++) { - - buffer[i + start] = floatBuffer[i]; - } - return count; - } - - @Override - public double getLatency() { - return blockingStream.getInfo().inputLatency; - } - - @Override - public int available() { - return blockingStream.getReadAvailable() * samplesPerFrame; - } - - } - - @Override - public String getName() { - return "JPortAudio"; - } -} diff --git a/src/com/jsyn/engine/LoadAnalyzer.java b/src/com/jsyn/engine/LoadAnalyzer.java deleted file mode 100644 index cbf7ed5..0000000 --- a/src/com/jsyn/engine/LoadAnalyzer.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * 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.engine; - -/** Measure CPU load. */ -public class LoadAnalyzer { - private long stopTime; - private long previousStopTime; - private long startTime; - private double averageTotalTime; - private double averageOnTime; - - protected LoadAnalyzer() { - stopTime = System.nanoTime(); - } - - /** - * Call this when you stop doing something. Ideally all of the time since start() was spent on - * doing something without interruption. - */ - public void stop() { - previousStopTime = stopTime; - stopTime = System.nanoTime(); - long onTime = stopTime - startTime; - long totalTime = stopTime - previousStopTime; - if (totalTime > 0) { - // Recursive averaging filter. - double rate = 0.01; - averageOnTime = (averageOnTime * (1.0 - rate)) + (onTime * rate); - averageTotalTime = (averageTotalTime * (1.0 - rate)) + (totalTime * rate); - } - } - - /** Call this when you start doing something. */ - public void start() { - startTime = System.nanoTime(); - } - - /** Calculate, on average, how much of the time was spent doing something. */ - public double getAverageLoad() { - if (averageTotalTime > 0.0) { - return averageOnTime / averageTotalTime; - } else { - return 0.0; - } - } -} diff --git a/src/com/jsyn/engine/MultiTable.java b/src/com/jsyn/engine/MultiTable.java deleted file mode 100644 index 6606639..0000000 --- a/src/com/jsyn/engine/MultiTable.java +++ /dev/null @@ -1,230 +0,0 @@ -/* - * 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.engine; - -/* - * Multiple tables of sawtooth data. - * organized by octaves below the Nyquist Rate. - * used to generate band-limited Sawtooth, Impulse, Pulse, Square and Triangle BL waveforms - * -
- Analysis of octave requirements for tables.
-
- OctavesIndex    Frequency     Partials
- 0               N/2  11025      1
- 1               N/4   5512      2
- 2               N/8   2756      4
- 3               N/16  1378      8
- 4               N/32   689      16
- 5               N/64   344      32
- 6               N/128  172      64
- 7               N/256   86      128
- 
- * - * @author Phil Burk (C) 2009 Mobileer Inc - */ -public class MultiTable { - - public final static int NUM_TABLES = 8; - public final static int CYCLE_SIZE = (1 << 10); - - private static MultiTable instance = new MultiTable(NUM_TABLES, CYCLE_SIZE); - private double phaseScalar; - private float[][] tables; // array of array of tables - - /************************************************************************** - * Initialize sawtooth wavetables. Table[0] should contain a pure sine wave. Succeeding tables - * should have increasing numbers of partials. - */ - public MultiTable(int numTables, int cycleSize) { - int tableSize = cycleSize + 1; - - // Allocate array of arrays. - tables = new float[numTables][tableSize]; - - float[] sineTable = tables[0]; - - phaseScalar = (float) (cycleSize * 0.5); - - /* Fill initial sine table with values for -PI to PI. */ - for (int j = 0; j < tableSize; j++) { - sineTable[j] = (float) Math.sin(((((double) j) / (double) cycleSize) * Math.PI * 2.0) - - Math.PI); - } - - /* - * Build each table from scratch and scale partials by raised cosine* to eliminate Gibbs - * effect. - */ - for (int i = 1; i < numTables; i++) { - int numPartials; - double kGibbs; - float[] table = tables[i]; - - /* Add together partials for this table. */ - numPartials = 1 << i; - kGibbs = Math.PI / (2 * numPartials); - for (int k = 0; k < numPartials; k++) { - double ampl, cGibbs; - int sineIndex = 0; - int partial = k + 1; - cGibbs = Math.cos(k * kGibbs); - /* Calculate amplitude for Nth partial */ - ampl = cGibbs * cGibbs / partial; - - for (int j = 0; j < tableSize; j++) { - table[j] += (float) ampl * sineTable[sineIndex]; - sineIndex += partial; - /* Wrap index at end of table.. */ - if (sineIndex >= cycleSize) { - sineIndex -= cycleSize; - } - } - } - } - - /* Normalize after */ - for (int i = 1; i < numTables; i++) { - normalizeArray(tables[i]); - } - } - - /**************************************************************************/ - public static float normalizeArray(float[] fdata) { - float max, val, gain; - int i; - - // determine maximum value. - max = 0.0f; - for (i = 0; i < fdata.length; i++) { - val = Math.abs(fdata[i]); - if (val > max) - max = val; - } - if (max < 0.0000001f) - max = 0.0000001f; - // scale array - gain = 1.0f / max; - for (i = 0; i < fdata.length; i++) - fdata[i] *= gain; - return gain; - } - - /***************************************************************************** - * When the phaseInc maps to the highest level table, then we start interpolating between the - * highest table and the raw sawtooth value (phase). When phaseInc points to highest table: - * flevel = NUM_TABLES - 1 = -1 - log2(pInc); log2(pInc) = - NUM_TABLES pInc = 2**(-NUM_TABLES) - */ - private final static double LOWEST_PHASE_INC_INV = (1 << NUM_TABLES); - - /**************************************************************************/ - /* Phase ranges from -1.0 to +1.0 */ - public double calculateSawtooth(double currentPhase, double positivePhaseIncrement, - double flevel) { - float[] tableBase; - double val; - double hiSam; /* Use when verticalFraction is 1.0 */ - double loSam; /* Use when verticalFraction is 0.0 */ - double sam1, sam2; - - /* Use Phase to determine sampleIndex into table. */ - double findex = ((phaseScalar * currentPhase) + phaseScalar); - // findex is > 0 so we do not need to call floor(). - int sampleIndex = (int) findex; - double horizontalFraction = findex - sampleIndex; - int tableIndex = (int) flevel; - - if (tableIndex > (NUM_TABLES - 2)) { - /* - * Just use top table and mix with arithmetic sawtooth if below lowest frequency. - * Generate new fraction for interpolating between 0.0 and lowest table frequency. - */ - double fraction = positivePhaseIncrement * LOWEST_PHASE_INC_INV; - tableBase = tables[(NUM_TABLES - 1)]; - - /* Get adjacent samples. Assume guard point present. */ - sam1 = tableBase[sampleIndex]; - sam2 = tableBase[sampleIndex + 1]; - /* Interpolate between adjacent samples. */ - loSam = sam1 + (horizontalFraction * (sam2 - sam1)); - - /* Use arithmetic version for low frequencies. */ - /* fraction is 0.0 at 0 Hz */ - val = currentPhase + (fraction * (loSam - currentPhase)); - } else { - - double verticalFraction = flevel - tableIndex; - - if (tableIndex < 0) { - if (tableIndex < -1) // above Nyquist! - { - val = 0.0; - } else { - /* - * At top of supported range, interpolate between 0.0 and first partial. - */ - tableBase = tables[0]; /* Sine wave table. */ - - /* Get adjacent samples. Assume guard point present. */ - sam1 = tableBase[sampleIndex]; - sam2 = tableBase[sampleIndex + 1]; - - /* Interpolate between adjacent samples. */ - hiSam = sam1 + (horizontalFraction * (sam2 - sam1)); - /* loSam = 0.0 */ - // verticalFraction is 0.0 at Nyquist - val = verticalFraction * hiSam; - } - } else { - /* - * Interpolate between adjacent levels to prevent harmonics from popping. - */ - tableBase = tables[tableIndex + 1]; - - /* Get adjacent samples. Assume guard point present. */ - sam1 = tableBase[sampleIndex]; - sam2 = tableBase[sampleIndex + 1]; - - /* Interpolate between adjacent samples. */ - hiSam = sam1 + (horizontalFraction * (sam2 - sam1)); - - /* Get adjacent samples. Assume guard point present. */ - tableBase = tables[tableIndex]; - sam1 = tableBase[sampleIndex]; - sam2 = tableBase[sampleIndex + 1]; - - /* Interpolate between adjacent samples. */ - loSam = sam1 + (horizontalFraction * (sam2 - sam1)); - - val = loSam + (verticalFraction * (hiSam - loSam)); - } - } - return val; - } - - public double convertPhaseIncrementToLevel(double positivePhaseIncrement) { - if (positivePhaseIncrement < 1.0e-30) { - positivePhaseIncrement = 1.0e-30; - } - return -1.0 - (Math.log(positivePhaseIncrement) / Math.log(2.0)); - } - - public static MultiTable getInstance() { - return instance; - } - -} diff --git a/src/com/jsyn/engine/SynthesisEngine.java b/src/com/jsyn/engine/SynthesisEngine.java deleted file mode 100644 index b49e78e..0000000 --- a/src/com/jsyn/engine/SynthesisEngine.java +++ /dev/null @@ -1,698 +0,0 @@ -/* - * 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.engine; - -import java.util.ArrayList; -import java.util.List; -import java.util.ListIterator; -import java.util.concurrent.CopyOnWriteArrayList; -import java.util.logging.Logger; - -import com.jsyn.JSyn; -import com.jsyn.Synthesizer; -import com.jsyn.devices.AudioDeviceFactory; -import com.jsyn.devices.AudioDeviceInputStream; -import com.jsyn.devices.AudioDeviceManager; -import com.jsyn.devices.AudioDeviceOutputStream; -import com.jsyn.unitgen.UnitGenerator; -import com.softsynth.shared.time.ScheduledCommand; -import com.softsynth.shared.time.ScheduledQueue; -import com.softsynth.shared.time.TimeStamp; - -//TODO Resolve problem with HearDAHDSR where "Rate" port.set is not reflected in knob. Engine not running. -//TODO new tutorial and docs on website -//TODO AutoStop on DAHDSR -//TODO Test/example SequentialData queueOn and queueOff - -//TODO Abstract device interface. File device! -//TODO Measure thread switching sync, performance for multi-core synthesis. Use 4 core pro. -//TODO Optimize SineOscillatorPhaseModulated -//TODO More circuits. -//TODO DC blocker -//TODO Swing scope probe UIs, auto ranging - -/** - * Internal implementation of JSyn Synthesizer. The public API is in the Synthesizer interface. This - * class might be used directly internally. - * - * @author Phil Burk (C) 2009 Mobileer Inc - * @see Synthesizer - */ -public class SynthesisEngine implements Synthesizer { - private final static int BLOCKS_PER_BUFFER = 8; - private final static int FRAMES_PER_BUFFER = Synthesizer.FRAMES_PER_BLOCK * BLOCKS_PER_BUFFER; - // I have measured JavaSound taking 1200 msec to close devices. - private static final int MAX_THREAD_STOP_TIME = 2000; - - public static final int DEFAULT_FRAME_RATE = 44100; - - private final AudioDeviceManager audioDeviceManager; - private EngineThread engineThread; - private final ScheduledQueue commandQueue = new ScheduledQueue(); - - private InterleavingBuffer inputBuffer; - private InterleavingBuffer outputBuffer; - private double inverseNyquist; - private long frameCount; - private boolean pullDataEnabled = true; - private boolean useRealTime = true; - private boolean started; - private int frameRate = DEFAULT_FRAME_RATE; - private double framePeriod = 1.0 / frameRate; - - // List of all units added to the synth. - private final ArrayList allUnitList = new ArrayList(); - // List of running units. - private final ArrayList runningUnitList = new ArrayList(); - // List of units stopping because of autoStop. - private final ArrayList stoppingUnitList = new ArrayList(); - - private LoadAnalyzer loadAnalyzer; - // private int numOutputChannels; - // private int numInputChannels; - private final CopyOnWriteArrayList audioTasks = new CopyOnWriteArrayList(); - private double mOutputLatency; - private double mInputLatency; - /** A fraction corresponding to exactly -96 dB. */ - public static final double DB96 = (1.0 / 63095.73444801943); - /** A fraction that is approximately -90.3 dB. Defined as 1 bit of an S16. */ - public static final double DB90 = (1.0 / (1 << 15)); - - static Logger logger = Logger.getLogger(SynthesisEngine.class.getName()); - - public SynthesisEngine(AudioDeviceManager audioDeviceManager) { - this.audioDeviceManager = audioDeviceManager; - } - - public SynthesisEngine() { - this(AudioDeviceFactory.createAudioDeviceManager()); - } - - @Override - public String getVersion() { - return JSyn.VERSION; - } - - @Override - public int getVersionCode() { - return JSyn.VERSION_CODE; - } - - @Override - public String toString() { - return "JSyn " + JSyn.VERSION_TEXT; - } - - public boolean isPullDataEnabled() { - return pullDataEnabled; - } - - /** - * If set true then audio data will be pulled from the output ports of connected unit - * generators. The final unit in a tree of units needs to be start()ed. - * - * @param pullDataEnabled - */ - public void setPullDataEnabled(boolean pullDataEnabled) { - this.pullDataEnabled = pullDataEnabled; - } - - private void setupAudioBuffers(int numInputChannels, int numOutputChannels) { - inputBuffer = new InterleavingBuffer(FRAMES_PER_BUFFER, Synthesizer.FRAMES_PER_BLOCK, - numInputChannels); - outputBuffer = new InterleavingBuffer(FRAMES_PER_BUFFER, Synthesizer.FRAMES_PER_BLOCK, - numOutputChannels); - } - - public void terminate() { - } - - class InterleavingBuffer { - private final double[] interleavedBuffer; - ChannelBlockBuffer[] blockBuffers; - - InterleavingBuffer(int framesPerBuffer, int framesPerBlock, int samplesPerFrame) { - interleavedBuffer = new double[framesPerBuffer * samplesPerFrame]; - // Allocate buffers for each channel of synthesis output. - blockBuffers = new ChannelBlockBuffer[samplesPerFrame]; - for (int i = 0; i < blockBuffers.length; i++) { - blockBuffers[i] = new ChannelBlockBuffer(framesPerBlock); - } - } - - int deinterleave(int inIndex) { - for (int jf = 0; jf < Synthesizer.FRAMES_PER_BLOCK; jf++) { - for (int iob = 0; iob < blockBuffers.length; iob++) { - ChannelBlockBuffer buffer = blockBuffers[iob]; - buffer.values[jf] = interleavedBuffer[inIndex++]; - } - } - return inIndex; - } - - int interleave(int outIndex) { - for (int jf = 0; jf < Synthesizer.FRAMES_PER_BLOCK; jf++) { - for (int iob = 0; iob < blockBuffers.length; iob++) { - ChannelBlockBuffer buffer = blockBuffers[iob]; - interleavedBuffer[outIndex++] = buffer.values[jf]; - } - } - return outIndex; - } - - public double[] getChannelBuffer(int i) { - return blockBuffers[i].values; - } - - public void clear() { - for (int i = 0; i < blockBuffers.length; i++) { - blockBuffers[i].clear(); - } - } - } - - class ChannelBlockBuffer { - private final double[] values; - - ChannelBlockBuffer(int framesPerBlock) { - values = new double[framesPerBlock]; - } - - void clear() { - for (int i = 0; i < values.length; i++) { - values[i] = 0.0f; - } - } - } - - @Override - public void start() { - // TODO Use constants. - start(DEFAULT_FRAME_RATE, -1, 0, -1, 2); - } - - @Override - public void start(int frameRate) { - // TODO Use constants. - start(frameRate, -1, 0, -1, 2); - } - - @Override - public synchronized void start(int frameRate, int inputDeviceID, int numInputChannels, - int outputDeviceID, int numOutputChannels) { - if (started) { - logger.info("JSyn already started."); - return; - } - - this.frameRate = frameRate; - this.framePeriod = 1.0 / frameRate; - - setupAudioBuffers(numInputChannels, numOutputChannels); - - logger.info("Pure Java JSyn from www.softsynth.com, rate = " + frameRate + ", " - + (useRealTime ? "RT" : "NON-RealTime") + ", " + JSyn.VERSION_TEXT); - - inverseNyquist = 2.0 / frameRate; - - if (useRealTime) { - engineThread = new EngineThread(inputDeviceID, numInputChannels, - outputDeviceID, numOutputChannels); - logger.fine("Synth thread old priority = " + engineThread.getPriority()); - int engineThreadPriority = engineThread.getPriority() + 2 > Thread.MAX_PRIORITY ? - Thread.MAX_PRIORITY : engineThread.getPriority() + 2; - engineThread.setPriority(engineThreadPriority); - logger.fine("Synth thread new priority = " + engineThread.getPriority()); - engineThread.start(); - } - - started = true; - } - - @Override - public boolean isRunning() { - Thread thread = engineThread; - return (thread != null) && thread.isAlive(); - } - - @Override - public synchronized void stop() { - if (!started) { - logger.info("JSyn already stopped."); - return; - } - - if (useRealTime) { - // Stop audio synthesis and all units. - if (engineThread != null) { - try { - // Interrupt now, otherwise audio thread will wait for audio I/O. - engineThread.requestStop(); - engineThread.join(MAX_THREAD_STOP_TIME); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - } - synchronized (runningUnitList) { - runningUnitList.clear(); - } - started = false; - } - - private class EngineThread extends Thread - { - private AudioDeviceOutputStream audioOutputStream; - private AudioDeviceInputStream audioInputStream; - private volatile boolean go = true; - - EngineThread(int inputDeviceID, int numInputChannels, - int outputDeviceID, int numOutputChannels) { - if (numInputChannels > 0) { - audioInputStream = audioDeviceManager.createInputStream(inputDeviceID, frameRate, - numInputChannels); - } - if (numOutputChannels > 0) { - audioOutputStream = audioDeviceManager.createOutputStream(outputDeviceID, - frameRate, numOutputChannels); - } - } - - public void requestStop() { - go = false; - interrupt(); - } - - @Override - public void run() { - logger.fine("JSyn synthesis thread starting."); - try { - if (audioInputStream != null) { - logger.finer("JSyn synthesis thread trying to start audio INPUT!"); - audioInputStream.start(); - mInputLatency = audioInputStream.getLatency(); - String msg = String.format("Input Latency in = %5.1f msec", - 1000 * mInputLatency); - logger.fine(msg); - } - if (audioOutputStream != null) { - logger.finer("JSyn synthesis thread trying to start audio OUTPUT!"); - audioOutputStream.start(); - mOutputLatency = audioOutputStream.getLatency(); - String msg = String.format("Output Latency = %5.1f msec", - 1000 * mOutputLatency); - logger.fine(msg); - // Buy some time while we fill the buffer. - audioOutputStream.write(outputBuffer.interleavedBuffer); - } - loadAnalyzer = new LoadAnalyzer(); - while (go) { - boolean throttled = false; - if (audioInputStream != null) { - // This call will block when the input is empty. - audioInputStream.read(inputBuffer.interleavedBuffer); - throttled = true; - } - - loadAnalyzer.start(); - runAudioTasks(); - generateNextBuffer(); - loadAnalyzer.stop(); - - if (audioOutputStream != null) { - // This call will block when the output is full. - audioOutputStream.write(outputBuffer.interleavedBuffer); - throttled = true; - } - if (!throttled && isRealTime()) { - Thread.sleep(2); // avoid spinning and eating up CPU - } - } - - } catch (Throwable e) { - e.printStackTrace(); - go = false; - - } finally { - logger.info("JSyn synthesis thread in finally code."); - // Stop audio system. - if (audioInputStream != null) { - audioInputStream.stop(); - } - if (audioOutputStream != null) { - audioOutputStream.stop(); - } - } - logger.fine("JSyn synthesis thread exiting."); - } - } - - private void runAudioTasks() { - for (Runnable task : audioTasks) { - task.run(); - } - } - - // TODO We need to implement a sharedSleeper like we use in JSyn1. - public void generateNextBuffer() { - int outIndex = 0; - int inIndex = 0; - for (int i = 0; i < BLOCKS_PER_BUFFER; i++) { - if (inputBuffer != null) { - inIndex = inputBuffer.deinterleave(inIndex); - } - - TimeStamp timeStamp = createTimeStamp(); - // Try putting this up here so incoming time-stamped events will get - // scheduled later. - processScheduledCommands(timeStamp); - clearBlockBuffers(); - synthesizeBuffer(); - - if (outputBuffer != null) { - outIndex = outputBuffer.interleave(outIndex); - } - frameCount += Synthesizer.FRAMES_PER_BLOCK; - } - } - - @Override - public double getCurrentTime() { - return frameCount * framePeriod; - } - - @Override - public TimeStamp createTimeStamp() { - return new TimeStamp(getCurrentTime()); - } - - private void processScheduledCommands(TimeStamp timeStamp) { - List timeList = commandQueue.removeNextList(timeStamp); - - while (timeList != null) { - while (!timeList.isEmpty()) { - ScheduledCommand command = timeList.remove(0); - logger.fine("processing " + command + ", at time " + timeStamp.getTime()); - command.run(); - } - // Get next list of commands at the given time. - timeList = commandQueue.removeNextList(timeStamp); - } - } - - @Override - public void scheduleCommand(TimeStamp timeStamp, ScheduledCommand command) { - if ((Thread.currentThread() == engineThread) && (timeStamp.getTime() <= getCurrentTime())) { - command.run(); - } else { - logger.fine("scheduling " + command + ", at time " + timeStamp.getTime()); - commandQueue.add(timeStamp, command); - } - } - - @Override - public void scheduleCommand(double time, ScheduledCommand command) { - TimeStamp timeStamp = new TimeStamp(time); - scheduleCommand(timeStamp, command); - } - - @Override - public void queueCommand(ScheduledCommand command) { - TimeStamp timeStamp = createTimeStamp(); - scheduleCommand(timeStamp, command); - } - - @Override - public void clearCommandQueue() { - commandQueue.clear(); - } - - private void clearBlockBuffers() { - outputBuffer.clear(); - } - - private void synthesizeBuffer() { - synchronized (runningUnitList) { - ListIterator iterator = runningUnitList.listIterator(); - while (iterator.hasNext()) { - UnitGenerator unit = iterator.next(); - if (pullDataEnabled) { - unit.pullData(getFrameCount(), 0, Synthesizer.FRAMES_PER_BLOCK); - } else { - unit.generate(0, Synthesizer.FRAMES_PER_BLOCK); - } - } - // Remove any units that got auto stopped. - for (UnitGenerator ugen : stoppingUnitList) { - runningUnitList.remove(ugen); - ugen.flattenOutputs(); - } - } - stoppingUnitList.clear(); - } - - public double[] getInputBuffer(int i) { - try { - return inputBuffer.getChannelBuffer(i); - } catch (ArrayIndexOutOfBoundsException e) { - throw new RuntimeException("Audio Input not configured in start() method."); - } - } - - public double[] getOutputBuffer(int i) { - try { - return outputBuffer.getChannelBuffer(i); - } catch (ArrayIndexOutOfBoundsException e) { - throw new RuntimeException("Audio Output not configured in start() method."); - } - } - - private void internalStopUnit(UnitGenerator unit) { - synchronized (runningUnitList) { - runningUnitList.remove(unit); - } - unit.flattenOutputs(); - } - - public void autoStopUnit(UnitGenerator unitGenerator) { - synchronized (stoppingUnitList) { - stoppingUnitList.add(unitGenerator); - } - } - - @Override - public void startUnit(UnitGenerator unit, double time) { - startUnit(unit, new TimeStamp(time)); - } - - @Override - public void stopUnit(UnitGenerator unit, double time) { - stopUnit(unit, new TimeStamp(time)); - } - - @Override - public void startUnit(final UnitGenerator unit, TimeStamp timeStamp) { - // Don't start if it is a component in a circuit because it will be - // executed by the circuit. - if (unit.getCircuit() == null) { - scheduleCommand(timeStamp, new ScheduledCommand() { - @Override - public void run() { - internalStartUnit(unit); - } - }); - } - } - - @Override - public void stopUnit(final UnitGenerator unit, TimeStamp timeStamp) { - scheduleCommand(timeStamp, new ScheduledCommand() { - @Override - public void run() { - internalStopUnit(unit); - } - }); - } - - @Override - public void startUnit(UnitGenerator unit) { - startUnit(unit, createTimeStamp()); - } - - @Override - public void stopUnit(UnitGenerator unit) { - stopUnit(unit, createTimeStamp()); - } - - private void internalStartUnit(UnitGenerator unit) { - // logger.info( "internalStartUnit " + unit + " with circuit " + - // unit.getCircuit() ); - if (unit.getCircuit() == null) { - synchronized (runningUnitList) { - if (!runningUnitList.contains(unit)) { - runningUnitList.add(unit); - } - } - } - // else - // { - // logger.info( - // "internalStartUnit detected race condition !!!! from old JSyn" + unit - // + " with circuit " + unit.getCircuit() ); - // } - } - - public double getInverseNyquist() { - return inverseNyquist; - } - - public double convertTimeToExponentialScaler(double duration) { - // Calculate scaler so that scaler^frames = target/source - double numFrames = duration * getFrameRate(); - return Math.pow(DB90, (1.0 / numFrames)); - } - - @Override - public long getFrameCount() { - return frameCount; - } - - /** - * @return the frameRate - */ - @Override - public int getFrameRate() { - return frameRate; - } - - /** - * @return the inverse of the frameRate for efficiency - */ - @Override - public double getFramePeriod() { - return framePeriod; - } - - /** Convert a short value to a double in the range -1.0 to almost 1.0. */ - public static double convertShortToDouble(short sdata) { - return (sdata * (1.0 / Short.MAX_VALUE)); - } - - /** - * Convert a double value in the range -1.0 to almost 1.0 to a short. Double value is clipped - * before converting. - */ - public static short convertDoubleToShort(double d) { - final double maxValue = ((double) (Short.MAX_VALUE - 1)) / Short.MAX_VALUE; - if (d > maxValue) { - d = maxValue; - } else if (d < -1.0) { - d = -1.0; - } - return (short) (d * Short.MAX_VALUE); - } - - @Override - public void addAudioTask(Runnable blockTask) { - audioTasks.add(blockTask); - } - - @Override - public void removeAudioTask(Runnable blockTask) { - audioTasks.remove(blockTask); - } - - @Override - public double getUsage() { - // use temp so we don't have to synchronize - LoadAnalyzer temp = loadAnalyzer; - if (temp != null) { - return temp.getAverageLoad(); - } else { - return 0.0; - } - } - - @Override - public AudioDeviceManager getAudioDeviceManager() { - return audioDeviceManager; - } - - @Override - public void setRealTime(boolean realTime) { - useRealTime = realTime; - } - - @Override - public boolean isRealTime() { - return useRealTime; - } - - public double getOutputLatency() { - return mOutputLatency; - } - - public double getInputLatency() { - return mInputLatency; - } - - @Override - public void add(UnitGenerator ugen) { - ugen.setSynthesisEngine(this); - allUnitList.add(ugen); - } - - @Override - public void remove(UnitGenerator ugen) { - allUnitList.remove(ugen); - } - - @Override - public void sleepUntil(double time) throws InterruptedException { - double timeToSleep = time - getCurrentTime(); - while (timeToSleep > 0.0) { - if (useRealTime) { - long msecToSleep = (long) (1000 * timeToSleep); - if (msecToSleep <= 0) { - msecToSleep = 1; - } - Thread.sleep(msecToSleep); - } else { - - generateNextBuffer(); - } - timeToSleep = time - getCurrentTime(); - } - } - - @Override - public void sleepFor(double duration) throws InterruptedException { - sleepUntil(getCurrentTime() + duration); - } - - public void printConnections() { - if (pullDataEnabled) { - ListIterator iterator = runningUnitList.listIterator(); - while (iterator.hasNext()) { - UnitGenerator unit = iterator.next(); - unit.printConnections(); - } - } - - } - -} diff --git a/src/com/jsyn/exceptions/ChannelMismatchException.java b/src/com/jsyn/exceptions/ChannelMismatchException.java deleted file mode 100644 index a1554cd..0000000 --- a/src/com/jsyn/exceptions/ChannelMismatchException.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2010 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.exceptions; - -/** - * This will get thrown if, for example, stereo data is queued to a mono player. - * - * @author Phil Burk (C) 2010 Mobileer Inc - */ -public class ChannelMismatchException extends RuntimeException { - - public ChannelMismatchException(String message) { - super(message); - } - - /** - * - */ - private static final long serialVersionUID = -5345224363387498119L; - -} diff --git a/src/com/jsyn/instruments/DrumWoodFM.java b/src/com/jsyn/instruments/DrumWoodFM.java deleted file mode 100644 index ba6cd1b..0000000 --- a/src/com/jsyn/instruments/DrumWoodFM.java +++ /dev/null @@ -1,159 +0,0 @@ -/* - * Copyright 2011 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.instruments; - -import com.jsyn.ports.UnitInputPort; -import com.jsyn.ports.UnitOutputPort; -import com.jsyn.unitgen.Add; -import com.jsyn.unitgen.Circuit; -import com.jsyn.unitgen.EnvelopeAttackDecay; -import com.jsyn.unitgen.Multiply; -import com.jsyn.unitgen.PassThrough; -import com.jsyn.unitgen.SineOscillator; -import com.jsyn.unitgen.SineOscillatorPhaseModulated; -import com.jsyn.unitgen.UnitVoice; -import com.jsyn.util.VoiceDescription; -import com.softsynth.shared.time.TimeStamp; - -/** - * Drum instruments using 2 Operator FM. - * - * @author Phil Burk (C) 2011 Mobileer Inc - */ -public class DrumWoodFM extends Circuit implements UnitVoice { - private static final int NUM_PRESETS = 3; - // Declare units and ports. - EnvelopeAttackDecay ampEnv; - SineOscillatorPhaseModulated carrierOsc; - EnvelopeAttackDecay modEnv; - SineOscillator modOsc; - PassThrough freqDistributor; - Add modSummer; - Multiply frequencyMultiplier; - - public UnitInputPort mcratio; - public UnitInputPort index; - public UnitInputPort modRange; - public UnitInputPort frequency; - - public DrumWoodFM() { - // Create unit generators. - add(carrierOsc = new SineOscillatorPhaseModulated()); - add(freqDistributor = new PassThrough()); - add(modSummer = new Add()); - add(ampEnv = new EnvelopeAttackDecay()); - add(modEnv = new EnvelopeAttackDecay()); - add(modOsc = new SineOscillator()); - add(frequencyMultiplier = new Multiply()); - - addPort(mcratio = frequencyMultiplier.inputB, "MCRatio"); - addPort(index = modSummer.inputA, "Index"); - addPort(modRange = modEnv.amplitude, "ModRange"); - addPort(frequency = freqDistributor.input, "Frequency"); - - ampEnv.export(this, "Amp"); - modEnv.export(this, "Mod"); - - freqDistributor.output.connect(carrierOsc.frequency); - freqDistributor.output.connect(frequencyMultiplier.inputA); - - carrierOsc.output.connect(ampEnv.amplitude); - modEnv.output.connect(modSummer.inputB); - modSummer.output.connect(modOsc.amplitude); - modOsc.output.connect(carrierOsc.modulation); - frequencyMultiplier.output.connect(modOsc.frequency); - - // Make the circuit turn off when the envelope finishes to reduce CPU load. - ampEnv.setupAutoDisable(this); - - usePreset(0); - } - - @Override - public void noteOff(TimeStamp timeStamp) { - } - - @Override - public void noteOn(double freq, double ampl, TimeStamp timeStamp) { - carrierOsc.amplitude.set(ampl, timeStamp); - ampEnv.input.trigger(timeStamp); - modEnv.input.trigger(timeStamp); - } - - @Override - public UnitOutputPort getOutput() { - return ampEnv.output; - } - - @Override - public void usePreset(int presetIndex) { - mcratio.setup(0.001, 0.6875, 20.0); - ampEnv.attack.setup(0.001, 0.005, 8.0); - modEnv.attack.setup(0.001, 0.005, 8.0); - - int n = presetIndex % NUM_PRESETS; - switch (n) { - case 0: - ampEnv.decay.setup(0.001, 0.293, 8.0); - modEnv.decay.setup(0.001, 0.07, 8.0); - frequency.setup(0.0, 349.0, 3000.0); - index.setup(0.001, 0.05, 10.0); - modRange.setup(0.001, 0.4, 10.0); - break; - case 1: - default: - ampEnv.decay.setup(0.001, 0.12, 8.0); - modEnv.decay.setup(0.001, 0.06, 8.0); - frequency.setup(0.0, 1400.0, 3000.0); - index.setup(0.001, 0.16, 10.0); - modRange.setup(0.001, 0.17, 10.0); - break; - } - } - - static class MyVoiceDescription extends VoiceDescription { - static String[] presetNames = { - "WoodBlockFM", "ClaveFM" - }; - static String[] tags = { - "electronic", "drum" - }; - - public MyVoiceDescription() { - super("DrumWoodFM", presetNames); - } - - @Override - public UnitVoice createUnitVoice() { - return new DrumWoodFM(); - } - - @Override - public String[] getTags(int presetIndex) { - return tags; - } - - @Override - public String getVoiceClassName() { - return DrumWoodFM.class.getName(); - } - } - - public static VoiceDescription getVoiceDescription() { - return new MyVoiceDescription(); - } -} diff --git a/src/com/jsyn/instruments/DualOscillatorSynthVoice.java b/src/com/jsyn/instruments/DualOscillatorSynthVoice.java deleted file mode 100644 index c81041f..0000000 --- a/src/com/jsyn/instruments/DualOscillatorSynthVoice.java +++ /dev/null @@ -1,301 +0,0 @@ -/* - * Copyright 2010 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.instruments; - -import com.jsyn.ports.UnitInputPort; -import com.jsyn.ports.UnitOutputPort; -import com.jsyn.unitgen.Add; -import com.jsyn.unitgen.Circuit; -import com.jsyn.unitgen.EnvelopeDAHDSR; -import com.jsyn.unitgen.FilterFourPoles; -import com.jsyn.unitgen.MorphingOscillatorBL; -import com.jsyn.unitgen.Multiply; -import com.jsyn.unitgen.UnitVoice; -import com.jsyn.util.VoiceDescription; -import com.softsynth.math.AudioMath; -import com.softsynth.shared.time.TimeStamp; - -/** - * Synthesizer voice with two morphing oscillators and a four-pole resonant filter. - * Modulate the amplitude and filter using DAHDSR envelopes. - */ -public class DualOscillatorSynthVoice extends Circuit implements UnitVoice { - private Multiply frequencyMultiplier; - private Multiply amplitudeMultiplier; - private Multiply detuneScaler1; - private Multiply detuneScaler2; - private Multiply amplitudeBoost; - private MorphingOscillatorBL osc1; - private MorphingOscillatorBL osc2; - private FilterFourPoles filter; - private EnvelopeDAHDSR ampEnv; - private EnvelopeDAHDSR filterEnv; - private Add cutoffAdder; - - private static MyVoiceDescription voiceDescription; - - public UnitInputPort amplitude; - public UnitInputPort frequency; - /** - * This scales the frequency value. You can use this to modulate a group of instruments using a - * shared LFO and they will stay in tune. Set to 1.0 for no modulation. - */ - public UnitInputPort frequencyScaler; - public UnitInputPort oscShape1; - public UnitInputPort oscShape2; -// public UnitInputPort oscDetune1; -// public UnitInputPort oscDetune2; - public UnitInputPort cutoff; - public UnitInputPort filterEnvDepth; - public UnitInputPort Q; - - public DualOscillatorSynthVoice() { - add(frequencyMultiplier = new Multiply()); - add(amplitudeMultiplier = new Multiply()); - add(amplitudeBoost = new Multiply()); - add(detuneScaler1 = new Multiply()); - add(detuneScaler2 = new Multiply()); - // Add tone generators. - add(osc1 = new MorphingOscillatorBL()); - add(osc2 = new MorphingOscillatorBL()); - - // Use an envelope to control the amplitude. - add(ampEnv = new EnvelopeDAHDSR()); - - // Use an envelope to control the filter cutoff. - add(filterEnv = new EnvelopeDAHDSR()); - add(filter = new FilterFourPoles()); - add(cutoffAdder = new Add()); - - filterEnv.output.connect(cutoffAdder.inputA); - cutoffAdder.output.connect(filter.frequency); - frequencyMultiplier.output.connect(detuneScaler1.inputA); - frequencyMultiplier.output.connect(detuneScaler2.inputA); - detuneScaler1.output.connect(osc1.frequency); - detuneScaler2.output.connect(osc2.frequency); - osc1.output.connect(amplitudeMultiplier.inputA); // mix oscillators - osc2.output.connect(amplitudeMultiplier.inputA); - amplitudeMultiplier.output.connect(filter.input); - filter.output.connect(amplitudeBoost.inputA); - amplitudeBoost.output.connect(ampEnv.amplitude); - - addPort(amplitude = amplitudeMultiplier.inputB, PORT_NAME_AMPLITUDE); - addPort(frequency = frequencyMultiplier.inputA, PORT_NAME_FREQUENCY); - addPort(oscShape1 = osc1.shape, "OscShape1"); - addPort(oscShape2 = osc2.shape, "OscShape2"); -// addPort(oscDetune1 = osc1.shape, "OscDetune1"); -// addPort(oscDetune2 = osc2.shape, "OscDetune2"); - addPort(cutoff = cutoffAdder.inputB, PORT_NAME_CUTOFF); - addPortAlias(cutoff, PORT_NAME_TIMBRE); - addPort(Q = filter.Q); - addPort(frequencyScaler = frequencyMultiplier.inputB, PORT_NAME_FREQUENCY_SCALER); - addPort(filterEnvDepth = filterEnv.amplitude, "FilterEnvDepth"); - - filterEnv.export(this, "Filter"); - ampEnv.export(this, "Amp"); - - frequency.setup(osc1.frequency); - frequencyScaler.setup(0.2, 1.0, 4.0); - cutoff.setup(filter.frequency); - // Allow negative filter sweeps - filterEnvDepth.setup(-4000.0, 2000.0, 4000.0); - - // set amplitudes slightly different so that they never entirely cancel - osc1.amplitude.set(0.5); - osc2.amplitude.set(0.4); - // Make the circuit turn off when the envelope finishes to reduce CPU load. - ampEnv.setupAutoDisable(this); - // Add named port for mapping pressure. - amplitudeBoost.inputB.setup(1.0, 1.0, 4.0); - addPortAlias(amplitudeBoost.inputB, PORT_NAME_PRESSURE); - - usePreset(0); - } - - /** - * The first oscillator will be tuned UP by semitoneOffset/2. - * The second oscillator will be tuned DOWN by semitoneOffset/2. - * @param semitoneOffset - */ - private void setDetunePitch(double semitoneOffset) { - double halfOffset = semitoneOffset * 0.5; - setDetunePitch1(halfOffset); - setDetunePitch2(-halfOffset); - } - - /** - * Set the detuning for osc1 in semitones. - * @param semitoneOffset - */ - private void setDetunePitch1(double semitoneOffset) { - double scale = AudioMath.semitonesToFrequencyScaler(semitoneOffset); - detuneScaler1.inputB.set(scale); - } - - /** - * Set the detuning for osc2 in semitones. - * @param semitoneOffset - */ - private void setDetunePitch2(double semitoneOffset) { - double scale = AudioMath.semitonesToFrequencyScaler(semitoneOffset); - detuneScaler2.inputB.set(scale); - } - - @Override - public void noteOff(TimeStamp timeStamp) { - ampEnv.input.off(timeStamp); - filterEnv.input.off(timeStamp); - } - - @Override - public void noteOn(double freq, double ampl, TimeStamp timeStamp) { - frequency.set(freq, timeStamp); - amplitude.set(ampl, timeStamp); - ampEnv.input.on(timeStamp); - filterEnv.input.on(timeStamp); - } - - @Override - public UnitOutputPort getOutput() { - return ampEnv.output; - } - - // Reset to basic voice. - public void reset() { - osc1.shape.set(0.0); - osc2.shape.set(0.0); - ampEnv.attack.set(0.005); - ampEnv.decay.set(0.2); - ampEnv.sustain.set(0.5); - ampEnv.release.set(1.0); - filterEnv.attack.set(0.01); - filterEnv.decay.set(0.6); - filterEnv.sustain.set(0.4); - filterEnv.release.set(1.0); - cutoff.set(500.0); - filterEnvDepth.set(3000.0); - filter.reset(); - filter.Q.set(3.9); - setDetunePitch(0.02); - } - - @Override - public void usePreset(int presetIndex) { - reset(); // start from known configuration - int n = presetIndex % presetNames.length; - switch (n) { - case 0: - break; - case 1: - ampEnv.attack.set(0.1); - ampEnv.decay.set(0.9); - ampEnv.sustain.set(0.1); - ampEnv.release.set(0.1); - cutoff.set(500.0); - filterEnvDepth.set(500.0); - filter.Q.set(3.0); - break; - case 2: - ampEnv.attack.set(0.1); - ampEnv.decay.set(0.3); - ampEnv.release.set(0.5); - cutoff.set(2000.0); - filterEnvDepth.set(500.0); - filter.Q.set(2.0); - break; - case 3: - osc1.shape.set(-0.9); - osc2.shape.set(-0.8); - ampEnv.attack.set(0.3); - ampEnv.decay.set(0.8); - ampEnv.release.set(0.2); - filterEnv.sustain.set(0.7); - cutoff.set(500.0); - filterEnvDepth.set(500.0); - filter.Q.set(3.0); - break; - case 4: - osc1.shape.set(1.0); - osc2.shape.set(0.0); - break; - case 5: - osc1.shape.set(1.0); - setDetunePitch1(0.0); - osc2.shape.set(0.9); - setDetunePitch1(7.0); - break; - case 6: - osc1.shape.set(0.6); - osc2.shape.set(-0.2); - setDetunePitch1(0.01); - ampEnv.attack.set(0.005); - ampEnv.decay.set(0.09); - ampEnv.sustain.set(0.0); - ampEnv.release.set(1.0); - filterEnv.attack.set(0.005); - filterEnv.decay.set(0.1); - filterEnv.sustain.set(0.4); - filterEnv.release.set(1.0); - cutoff.set(2000.0); - filterEnvDepth.set(5000.0); - filter.Q.set(7.02); - break; - default: - break; - } - } - - private static final String[] presetNames = { - "FastSaw", "SlowSaw", "BrightSaw", - "SoftSine", "SquareSaw", "SquareFifth", - "Blip" - }; - - static class MyVoiceDescription extends VoiceDescription { - String[] tags = { - "electronic", "filter", "analog", "subtractive" - }; - - public MyVoiceDescription() { - super(DualOscillatorSynthVoice.class.getName(), presetNames); - } - - @Override - public UnitVoice createUnitVoice() { - return new DualOscillatorSynthVoice(); - } - - @Override - public String[] getTags(int presetIndex) { - return tags; - } - - @Override - public String getVoiceClassName() { - return DualOscillatorSynthVoice.class.getName(); - } - } - - public static VoiceDescription getVoiceDescription() { - if (voiceDescription == null) { - voiceDescription = new MyVoiceDescription(); - } - return voiceDescription; - } - - -} diff --git a/src/com/jsyn/instruments/JSynInstrumentLibrary.java b/src/com/jsyn/instruments/JSynInstrumentLibrary.java deleted file mode 100644 index 9f111c3..0000000 --- a/src/com/jsyn/instruments/JSynInstrumentLibrary.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2011 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.instruments; - -import com.jsyn.swing.InstrumentBrowser; -import com.jsyn.util.InstrumentLibrary; -import com.jsyn.util.VoiceDescription; - -/** - * Stock instruments provided with the JSyn distribution. - * - * @author Phil Burk (C) 2011 Mobileer Inc - * @see InstrumentBrowser - */ - -public class JSynInstrumentLibrary implements InstrumentLibrary { - static VoiceDescription[] descriptions = { - WaveShapingVoice.getVoiceDescription(), - SubtractiveSynthVoice.getVoiceDescription(), - DualOscillatorSynthVoice.getVoiceDescription(), - NoiseHit.getVoiceDescription(), - DrumWoodFM.getVoiceDescription() - }; - - @Override - public VoiceDescription[] getVoiceDescriptions() { - return descriptions; - } - - @Override - public String getName() { - return "JSynInstruments"; - } -} diff --git a/src/com/jsyn/instruments/NoiseHit.java b/src/com/jsyn/instruments/NoiseHit.java deleted file mode 100644 index b8714fc..0000000 --- a/src/com/jsyn/instruments/NoiseHit.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * 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.instruments; - -import com.jsyn.ports.UnitOutputPort; -import com.jsyn.unitgen.Circuit; -import com.jsyn.unitgen.EnvelopeAttackDecay; -import com.jsyn.unitgen.PinkNoise; -import com.jsyn.unitgen.UnitVoice; -import com.jsyn.util.VoiceDescription; -import com.softsynth.shared.time.TimeStamp; - -/** - * Cheap synthetic cymbal sound. - */ -public class NoiseHit extends Circuit implements UnitVoice { - EnvelopeAttackDecay ampEnv; - PinkNoise noise; - private static final int NUM_PRESETS = 3; - - public NoiseHit() { - // Create unit generators. - add(noise = new PinkNoise()); - add(ampEnv = new EnvelopeAttackDecay()); - noise.output.connect(ampEnv.amplitude); - - ampEnv.export(this, "Amp"); - - // Make the circuit turn off when the envelope finishes to reduce CPU load. - ampEnv.setupAutoDisable(this); - - usePreset(0); - } - - @Override - public void noteOff(TimeStamp timeStamp) { - } - - @Override - public void noteOn(double freq, double ampl, TimeStamp timeStamp) { - noise.amplitude.set(ampl, timeStamp); - ampEnv.input.trigger(); - } - - @Override - public UnitOutputPort getOutput() { - return ampEnv.output; - } - - @Override - public void usePreset(int presetIndex) { - int n = presetIndex % NUM_PRESETS; - switch (n) { - case 0: - ampEnv.attack.set(0.001); - ampEnv.decay.set(0.1); - break; - case 1: - ampEnv.attack.set(0.03); - ampEnv.decay.set(1.4); - break; - default: - ampEnv.attack.set(0.9); - ampEnv.decay.set(0.3); - break; - } - } - - static class MyVoiceDescription extends VoiceDescription { - static String[] presetNames = { - "ShortNoiseHit", "LongNoiseHit", "SlowNoiseHit" - }; - static String[] tags = { - "electronic", "noise" - }; - - public MyVoiceDescription() { - super("NoiseHit", presetNames); - } - - @Override - public UnitVoice createUnitVoice() { - return new NoiseHit(); - } - - @Override - public String[] getTags(int presetIndex) { - return tags; - } - - @Override - public String getVoiceClassName() { - return NoiseHit.class.getName(); - } - } - - public static VoiceDescription getVoiceDescription() { - return new MyVoiceDescription(); - } -} diff --git a/src/com/jsyn/instruments/SubtractiveSynthVoice.java b/src/com/jsyn/instruments/SubtractiveSynthVoice.java deleted file mode 100644 index 5cfc4b9..0000000 --- a/src/com/jsyn/instruments/SubtractiveSynthVoice.java +++ /dev/null @@ -1,182 +0,0 @@ -/* - * Copyright 2010 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.instruments; - -import com.jsyn.ports.UnitInputPort; -import com.jsyn.ports.UnitOutputPort; -import com.jsyn.unitgen.Add; -import com.jsyn.unitgen.Circuit; -import com.jsyn.unitgen.EnvelopeDAHDSR; -import com.jsyn.unitgen.FilterLowPass; -import com.jsyn.unitgen.Multiply; -import com.jsyn.unitgen.SawtoothOscillatorBL; -import com.jsyn.unitgen.UnitOscillator; -import com.jsyn.unitgen.UnitVoice; -import com.jsyn.util.VoiceDescription; -import com.softsynth.shared.time.TimeStamp; - -/** - * Typical synthesizer voice with one oscillator and a biquad resonant filter. Modulate the amplitude and - * filter using DAHDSR envelopes. - * - * @author Phil Burk (C) 2010 Mobileer Inc - */ -public class SubtractiveSynthVoice extends Circuit implements UnitVoice { - private UnitOscillator osc; - private FilterLowPass filter; - private EnvelopeDAHDSR ampEnv; - private EnvelopeDAHDSR filterEnv; - private Add cutoffAdder; - private Multiply frequencyScaler; - - public UnitInputPort amplitude; - public UnitInputPort frequency; - /** - * This scales the frequency value. You can use this to modulate a group of instruments using a - * shared LFO and they will stay in tune. - */ - public UnitInputPort pitchModulation; - public UnitInputPort cutoff; - public UnitInputPort cutoffRange; - public UnitInputPort Q; - - public SubtractiveSynthVoice() { - add(frequencyScaler = new Multiply()); - // Add a tone generator. - add(osc = new SawtoothOscillatorBL()); - - // Use an envelope to control the amplitude. - add(ampEnv = new EnvelopeDAHDSR()); - - // Use an envelope to control the filter cutoff. - add(filterEnv = new EnvelopeDAHDSR()); - add(filter = new FilterLowPass()); - add(cutoffAdder = new Add()); - - filterEnv.output.connect(cutoffAdder.inputA); - cutoffAdder.output.connect(filter.frequency); - frequencyScaler.output.connect(osc.frequency); - osc.output.connect(filter.input); - filter.output.connect(ampEnv.amplitude); - - addPort(amplitude = osc.amplitude, "Amplitude"); - addPort(frequency = frequencyScaler.inputA, "Frequency"); - addPort(pitchModulation = frequencyScaler.inputB, "PitchMod"); - addPort(cutoff = cutoffAdder.inputB, "Cutoff"); - addPort(cutoffRange = filterEnv.amplitude, "CutoffRange"); - addPort(Q = filter.Q); - - ampEnv.export(this, "Amp"); - filterEnv.export(this, "Filter"); - - frequency.setup(osc.frequency); - pitchModulation.setup(0.2, 1.0, 4.0); - cutoff.setup(filter.frequency); - cutoffRange.setup(filter.frequency); - - // Make the circuit turn off when the envelope finishes to reduce CPU load. - ampEnv.setupAutoDisable(this); - - usePreset(0); - } - - @Override - public void noteOff(TimeStamp timeStamp) { - ampEnv.input.off(timeStamp); - filterEnv.input.off(timeStamp); - } - - @Override - public void noteOn(double freq, double ampl, TimeStamp timeStamp) { - frequency.set(freq, timeStamp); - amplitude.set(ampl, timeStamp); - - ampEnv.input.on(timeStamp); - filterEnv.input.on(timeStamp); - } - - @Override - public UnitOutputPort getOutput() { - return ampEnv.output; - } - - @Override - public void usePreset(int presetIndex) { - int n = presetIndex % presetNames.length; - switch (n) { - case 0: - ampEnv.attack.set(0.01); - ampEnv.decay.set(0.2); - ampEnv.release.set(1.0); - cutoff.set(500.0); - cutoffRange.set(500.0); - filter.Q.set(1.0); - break; - case 1: - ampEnv.attack.set(0.5); - ampEnv.decay.set(0.3); - ampEnv.release.set(0.2); - cutoff.set(500.0); - cutoffRange.set(500.0); - filter.Q.set(3.0); - break; - case 2: - default: - ampEnv.attack.set(0.1); - ampEnv.decay.set(0.3); - ampEnv.release.set(0.5); - cutoff.set(2000.0); - cutoffRange.set(500.0); - filter.Q.set(2.0); - break; - } - } - - static String[] presetNames = { - "FastSaw", "SlowSaw", "BrightSaw" - }; - - static class MyVoiceDescription extends VoiceDescription { - String[] tags = { - "electronic", "filter", "clean" - }; - - public MyVoiceDescription() { - super("SubtractiveSynth", presetNames); - } - - @Override - public UnitVoice createUnitVoice() { - return new SubtractiveSynthVoice(); - } - - @Override - public String[] getTags(int presetIndex) { - return tags; - } - - @Override - public String getVoiceClassName() { - return SubtractiveSynthVoice.class.getName(); - } - } - - public static VoiceDescription getVoiceDescription() { - return new MyVoiceDescription(); - } - -} diff --git a/src/com/jsyn/instruments/WaveShapingVoice.java b/src/com/jsyn/instruments/WaveShapingVoice.java deleted file mode 100644 index 5044f21..0000000 --- a/src/com/jsyn/instruments/WaveShapingVoice.java +++ /dev/null @@ -1,187 +0,0 @@ -/* - * Copyright 2011 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.instruments; - -import com.jsyn.data.DoubleTable; -import com.jsyn.ports.UnitFunctionPort; -import com.jsyn.ports.UnitInputPort; -import com.jsyn.ports.UnitOutputPort; -import com.jsyn.unitgen.Circuit; -import com.jsyn.unitgen.EnvelopeDAHDSR; -import com.jsyn.unitgen.FunctionEvaluator; -import com.jsyn.unitgen.Multiply; -import com.jsyn.unitgen.SineOscillator; -import com.jsyn.unitgen.UnitOscillator; -import com.jsyn.unitgen.UnitVoice; -import com.jsyn.util.VoiceDescription; -import com.softsynth.math.ChebyshevPolynomial; -import com.softsynth.math.PolynomialTableData; -import com.softsynth.shared.time.TimeStamp; - -/** - * Waveshaping oscillator with envelopes. - * - * @author Phil Burk (C) 2011 Mobileer Inc - */ -public class WaveShapingVoice extends Circuit implements UnitVoice { - private static final long serialVersionUID = -2704222221111608377L; - private static final int NUM_PRESETS = 3; - private UnitOscillator osc; - private FunctionEvaluator waveShaper; - private EnvelopeDAHDSR ampEnv; - private EnvelopeDAHDSR rangeEnv; - private Multiply frequencyScaler; - - public UnitInputPort range; - public UnitInputPort frequency; - public UnitInputPort amplitude; - public UnitFunctionPort function; - public UnitInputPort pitchModulation; - - // default Chebyshev polynomial table to share. - private static DoubleTable chebyshevTable; - private final static int CHEBYSHEV_ORDER = 11; - - static { - // Make table with Chebyshev polynomial to share among voices - PolynomialTableData chebData = new PolynomialTableData( - ChebyshevPolynomial.T(CHEBYSHEV_ORDER), 1024); - chebyshevTable = new DoubleTable(chebData.getData()); - } - - public WaveShapingVoice() { - add(frequencyScaler = new Multiply()); - add(osc = new SineOscillator()); - add(waveShaper = new FunctionEvaluator()); - add(rangeEnv = new EnvelopeDAHDSR()); - add(ampEnv = new EnvelopeDAHDSR()); - - addPort(amplitude = ampEnv.amplitude); - addPort(range = osc.amplitude, "Range"); - addPort(function = waveShaper.function); - addPort(frequency = frequencyScaler.inputA, "Frequency"); - addPort(pitchModulation = frequencyScaler.inputB, "PitchMod"); - - ampEnv.export(this, "Amp"); - rangeEnv.export(this, "Range"); - - function.set(chebyshevTable); - - // Connect units. - osc.output.connect(rangeEnv.amplitude); - rangeEnv.output.connect(waveShaper.input); - ampEnv.output.connect(waveShaper.amplitude); - frequencyScaler.output.connect(osc.frequency); - - // Set reasonable defaults for the ports. - pitchModulation.setup(0.1, 1.0, 10.0); - range.setup(0.1, 0.8, 1.0); - frequency.setup(osc.frequency); - amplitude.setup(0.0, 0.5, 1.0); - - // Make the circuit turn off when the envelope finishes to reduce CPU load. - ampEnv.setupAutoDisable(this); - - usePreset(2); - } - - @Override - public UnitOutputPort getOutput() { - return waveShaper.output; - } - - @Override - public void noteOn(double freq, double amp, TimeStamp timeStamp) { - frequency.set(freq, timeStamp); - amplitude.set(amp, timeStamp); - ampEnv.input.on(timeStamp); - rangeEnv.input.on(timeStamp); - } - - @Override - public void noteOff(TimeStamp timeStamp) { - ampEnv.input.off(timeStamp); - rangeEnv.input.off(timeStamp); - } - - @Override - public void usePreset(int presetIndex) { - int n = presetIndex % NUM_PRESETS; - switch (n) { - case 0: - ampEnv.attack.set(0.01); - ampEnv.decay.set(0.2); - ampEnv.release.set(1.0); - rangeEnv.attack.set(0.01); - rangeEnv.decay.set(0.2); - rangeEnv.sustain.set(0.4); - rangeEnv.release.set(1.0); - break; - case 1: - ampEnv.attack.set(0.5); - ampEnv.decay.set(0.3); - ampEnv.release.set(0.2); - rangeEnv.attack.set(0.03); - rangeEnv.decay.set(0.2); - rangeEnv.sustain.set(0.5); - rangeEnv.release.set(1.0); - break; - default: - ampEnv.attack.set(0.1); - ampEnv.decay.set(0.3); - ampEnv.release.set(0.5); - rangeEnv.attack.set(0.01); - rangeEnv.decay.set(0.2); - rangeEnv.sustain.set(0.9); - rangeEnv.release.set(1.0); - break; - } - } - - static class MyVoiceDescription extends VoiceDescription { - static String[] presetNames = { - "FastChebyshev", "SlowChebyshev", "BrightChebyshev" - }; - static String[] tags = { - "electronic", "waveshaping", "clean" - }; - - public MyVoiceDescription() { - super("Waveshaping", presetNames); - } - - @Override - public UnitVoice createUnitVoice() { - return new WaveShapingVoice(); - } - - @Override - public String[] getTags(int presetIndex) { - return tags; - } - - @Override - public String getVoiceClassName() { - return WaveShapingVoice.class.getName(); - } - } - - public static VoiceDescription getVoiceDescription() { - return new MyVoiceDescription(); - } - -} diff --git a/src/com/jsyn/io/AudioFifo.java b/src/com/jsyn/io/AudioFifo.java deleted file mode 100644 index 0c563e4..0000000 --- a/src/com/jsyn/io/AudioFifo.java +++ /dev/null @@ -1,204 +0,0 @@ -/* - * Copyright 2010 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.io; - -import java.util.concurrent.locks.Condition; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; - -/** - * FIFO that implements AudioInputStream, AudioOutputStream interfaces. This can be used to send - * audio data between different threads. The reads or writes may or may not wait based on flags. - * - * @author Phil Burk (C) 2010 Mobileer Inc - */ -public class AudioFifo implements AudioInputStream, AudioOutputStream { - // These indices run double the FIFO size so that we can tell empty from full. - private volatile int readIndex; - private volatile int writeIndex; - private volatile double[] buffer; - // Used to mask the index into range when accessing the buffer array. - private int accessMask; - // Used to mask the index so it wraps around. - private int sizeMask; - private boolean writeWaitEnabled = true; - private boolean readWaitEnabled = true; - final Lock lock = new ReentrantLock(); - final Condition notFull = lock.newCondition(); - final Condition notEmpty = lock.newCondition(); - - /** - * @param size Number of doubles in the FIFO. Must be a power of 2. Eg. 1024. - */ - public void allocate(int size) { - if (!isPowerOfTwo(size)) { - throw new IllegalArgumentException("Size must be a power of two."); - } - buffer = new double[size]; - accessMask = size - 1; - sizeMask = (size * 2) - 1; - } - - public int size() { - return buffer.length; - } - - public static boolean isPowerOfTwo(int size) { - return ((size & (size - 1)) == 0); - } - - /** How many samples are available for reading without blocking? */ - @Override - public int available() { - return (writeIndex - readIndex) & sizeMask; - } - - @Override - public void close() { - // TODO Maybe we should tell any thread that is waiting that the FIFO is closed. - } - - @Override - public double read() { - double value = Double.NaN; - if (readWaitEnabled) { - lock.lock(); - try { - while (available() < 1) { - try { - notEmpty.await(); - } catch (InterruptedException e) { - return Double.NaN; - } - } - value = readOneInternal(); - } finally { - lock.unlock(); - } - - } else { - if (readIndex != writeIndex) { - value = readOneInternal(); - } - } - - if (writeWaitEnabled) { - lock.lock(); - notFull.signal(); - lock.unlock(); - } - - return value; - } - - private double readOneInternal() { - double value = buffer[readIndex & accessMask]; - readIndex = (readIndex + 1) & sizeMask; - return value; - } - - @Override - public void write(double value) { - if (writeWaitEnabled) { - lock.lock(); - try { - while (available() == buffer.length) - { - try { - notFull.await(); - } catch (InterruptedException e) { - return; // Silently fail - } - } - writeOneInternal(value); - } finally { - lock.unlock(); - } - - } else { - if (available() != buffer.length) { - writeOneInternal(value); - } - } - - if (readWaitEnabled) { - lock.lock(); - notEmpty.signal(); - lock.unlock(); - } - } - - private void writeOneInternal(double value) { - buffer[writeIndex & accessMask] = value; - writeIndex = (writeIndex + 1) & sizeMask; - } - - @Override - public int read(double[] buffer) { - return read(buffer, 0, buffer.length); - } - - @Override - public int read(double[] buffer, int start, int count) { - if (readWaitEnabled) { - for (int i = 0; i < count; i++) { - buffer[i + start] = read(); - } - } else { - if (available() < count) { - count = available(); - } else { - for (int i = 0; i < count; i++) { - buffer[i + start] = read(); - } - } - } - return count; - } - - @Override - public void write(double[] buffer) { - write(buffer, 0, buffer.length); - } - - @Override - public void write(double[] buffer, int start, int count) { - for (int i = 0; i < count; i++) { - write(buffer[i + start]); - } - } - - /** If true then a subsequent write call will wait if there is no room to write. */ - public void setWriteWaitEnabled(boolean enabled) { - writeWaitEnabled = enabled; - - } - - /** If true then a subsequent read call will wait if there is no data to read. */ - public void setReadWaitEnabled(boolean enabled) { - readWaitEnabled = enabled; - - } - - public boolean isWriteWaitEnabled() { - return writeWaitEnabled; - } - - public boolean isReadWaitEnabled() { - return readWaitEnabled; - } -} diff --git a/src/com/jsyn/io/AudioInputStream.java b/src/com/jsyn/io/AudioInputStream.java deleted file mode 100644 index f233ff1..0000000 --- a/src/com/jsyn/io/AudioInputStream.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * 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.io; - -public interface AudioInputStream { - public double read(); - - /** - * Try to fill the entire buffer. - * - * @param buffer - * @return number of samples read - */ - public int read(double[] buffer); - - /** - * Read from the stream. Block until some data is available. - * - * @param buffer - * @param start index of first sample in buffer - * @param count number of samples to read, for example count=8 for 4 stereo frames - * @return number of samples read - */ - public int read(double[] buffer, int start, int count); - - public void close(); - - /** - * @return number of samples currently available to read without blocking - */ - public int available(); -} diff --git a/src/com/jsyn/io/AudioOutputStream.java b/src/com/jsyn/io/AudioOutputStream.java deleted file mode 100644 index dada577..0000000 --- a/src/com/jsyn/io/AudioOutputStream.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * 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.io; - -import java.io.IOException; - -public interface AudioOutputStream { - public void write(double value) throws IOException; - - public void write(double[] buffer) throws IOException; - - public void write(double[] buffer, int start, int count) throws IOException; - - public void close() throws IOException; -} diff --git a/src/com/jsyn/midi/MessageParser.java b/src/com/jsyn/midi/MessageParser.java deleted file mode 100644 index d0f5d4d..0000000 --- a/src/com/jsyn/midi/MessageParser.java +++ /dev/null @@ -1,147 +0,0 @@ -/* - * Copyright 2010 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.midi; - -/** - * Parse the message and call the appropriate method to handle it. - * - * @author Phil Burk (C) 2010 Mobileer Inc - */ -public class MessageParser { - private int[] parameterIndices = new int[MidiConstants.MAX_CHANNELS]; - private int[] parameterValues = new int[MidiConstants.MAX_CHANNELS]; - private int BIT_NON_RPM = 1 << 14; - private int MASK_14BIT = (1 << 14) - 1; - - public void parse(byte[] message) { - int status = message[0]; - int command = status & 0xF0; - int channel = status & 0x0F; - - switch (command) { - case MidiConstants.NOTE_ON: - int velocity = message[2]; - if (velocity == 0) { - noteOff(channel, message[1], velocity); - } else { - noteOn(channel, message[1], velocity); - } - break; - - case MidiConstants.NOTE_OFF: - noteOff(channel, message[1], message[2]); - break; - - case MidiConstants.POLYPHONIC_AFTERTOUCH: - polyphonicAftertouch(channel, message[1], message[2]); - break; - - case MidiConstants.CHANNEL_PRESSURE: - channelPressure(channel, message[1]); - break; - - case MidiConstants.CONTROL_CHANGE: - rawControlChange(channel, message[1], message[2]); - break; - - case MidiConstants.PROGRAM_CHANGE: - programChange(channel, message[1]); - break; - - case MidiConstants.PITCH_BEND: - int bend = (message[2] << 7) + message[1]; - pitchBend(channel, bend); - break; - } - - } - - public void rawControlChange(int channel, int index, int value) { - int paramIndex; - int paramValue; - switch(index) { - case MidiConstants.CONTROLLER_DATA_ENTRY: - parameterValues[channel] = value << 7; - fireParameterChange(channel); - break; - case MidiConstants.CONTROLLER_DATA_ENTRY_LSB: - paramValue = parameterValues[channel] & ~0x7F; - paramValue |= value; - parameterValues[channel] = paramValue; - fireParameterChange(channel); - break; - case MidiConstants.CONTROLLER_NRPN_LSB: - paramIndex = parameterIndices[channel] & ~0x7F; - paramIndex |= value | BIT_NON_RPM; - parameterIndices[channel] = paramIndex; - break; - case MidiConstants.CONTROLLER_NRPN_MSB: - parameterIndices[channel] = (value << 7) | BIT_NON_RPM;; - break; - case MidiConstants.CONTROLLER_RPN_LSB: - paramIndex = parameterIndices[channel] & ~0x7F; - paramIndex |= value; - parameterIndices[channel] = paramIndex; - break; - case MidiConstants.CONTROLLER_RPN_MSB: - parameterIndices[channel] = value << 7; - break; - default: - controlChange(channel, index, value); - break; - - } - } - - private void fireParameterChange(int channel) { - int paramIndex; - paramIndex = parameterIndices[channel]; - if ((paramIndex & BIT_NON_RPM) == 0) { - registeredParameter(channel, paramIndex, parameterValues[channel]); - } else { - nonRegisteredParameter(channel, paramIndex & MASK_14BIT, parameterValues[channel]); - } - } - - public void nonRegisteredParameter(int channel, int index14, int value14) { - } - - public void registeredParameter(int channel, int index14, int value14) { - } - - public void pitchBend(int channel, int bend) { - } - - public void programChange(int channel, int program) { - } - - public void polyphonicAftertouch(int channel, int pitch, int pressure) { - } - - public void channelPressure(int channel, int pressure) { - } - - public void controlChange(int channel, int index, int value) { - } - - public void noteOn(int channel, int pitch, int velocity) { - } - - // If a NOTE_ON with zero velocity is received then noteOff will be called. - public void noteOff(int channel, int pitch, int velocity) { - } -} diff --git a/src/com/jsyn/midi/MidiConstants.java b/src/com/jsyn/midi/MidiConstants.java deleted file mode 100644 index 8c92119..0000000 --- a/src/com/jsyn/midi/MidiConstants.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright 2010 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.midi; - -/** - * Constants that define the MIDI standard. - * - * @author Phil Burk (C) 2010 Mobileer Inc - */ -public class MidiConstants { - - public static final int MAX_CHANNELS = 16; - // Basic commands. - public static final int NOTE_OFF = 0x80; - public static final int NOTE_ON = 0x90; - public static final int POLYPHONIC_AFTERTOUCH = 0xA0; - public static final int CONTROL_CHANGE = 0xB0; - public static final int PROGRAM_CHANGE = 0xC0; - public static final int CHANNEL_AFTERTOUCH = 0xD0; - public static final int CHANNEL_PRESSURE = CHANNEL_AFTERTOUCH; - public static final int PITCH_BEND = 0xE0; - public static final int SYSTEM_COMMON = 0xF0; - - public static final int PITCH_BEND_CENTER = 0x2000; - - public static final int CONTROLLER_BANK_SELECT = 0; - public static final int CONTROLLER_MOD_WHEEL = 1; - public static final int CONTROLLER_BREATH = 2; - public static final int CONTROLLER_DATA_ENTRY = 6; - public static final int CONTROLLER_VOLUME = 7; - public static final int CONTROLLER_PAN = 10; - - public static final int CONTROLLER_LSB_OFFSET = 32; - public static final int CONTROLLER_DATA_ENTRY_LSB = CONTROLLER_DATA_ENTRY + CONTROLLER_LSB_OFFSET; - - public static final int CONTROLLER_TIMBRE = 74; // Often used by MPE for Y axis control. - - public static final int CONTROLLER_DATA_INCREMENT = 96; - public static final int CONTROLLER_DATA_DECREMENT = 97; - public static final int CONTROLLER_NRPN_LSB = 98; - public static final int CONTROLLER_NRPN_MSB = 99; - public static final int CONTROLLER_RPN_LSB = 100; - public static final int CONTROLLER_RPN_MSB = 101; - - public static final int RPN_BEND_RANGE = 0; - public static final int RPN_FINE_TUNING = 1; - - public static final String PITCH_NAMES[] = { - "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B" - }; - - /** - * Calculate frequency in Hertz based on MIDI pitch. Middle C is 60.0. You can use fractional - * pitches so 60.5 would give you a pitch half way between C and C#. - */ - static final double CONCERT_A_FREQUENCY = 440.0; - static final double CONCERT_A_PITCH = 69.0; - - public static double convertPitchToFrequency(double pitch) { - return CONCERT_A_FREQUENCY * Math.pow(2.0, ((pitch - CONCERT_A_PITCH) / 12.0)); - } - - /** - * Calculate MIDI pitch based on frequency in Hertz. Middle C is 60.0. - */ - public static double convertFrequencyToPitch(double frequency) { - return CONCERT_A_PITCH + (12 * Math.log(frequency / CONCERT_A_FREQUENCY) / Math.log(2.0)); - } - -} diff --git a/src/com/jsyn/midi/MidiSynthesizer.java b/src/com/jsyn/midi/MidiSynthesizer.java deleted file mode 100644 index 30204b0..0000000 --- a/src/com/jsyn/midi/MidiSynthesizer.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright 2016 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.midi; - -import com.jsyn.instruments.DualOscillatorSynthVoice; -import com.jsyn.util.MultiChannelSynthesizer; - -/** - * Map MIDI messages into calls to a MultiChannelSynthesizer. - * Handles CONTROLLER_MOD_WHEEL, TIMBRE, VOLUME and PAN. - * Handles Bend Range RPN. - * - *

-    voiceDescription = DualOscillatorSynthVoice.getVoiceDescription();
-    multiSynth = new MultiChannelSynthesizer();
-    final int startChannel = 0;
-    multiSynth.setup(synth, startChannel, NUM_CHANNELS, VOICES_PER_CHANNEL, voiceDescription);
-    midiSynthesizer = new MidiSynthesizer(multiSynth);
-    // pass MIDI bytes
-    midiSynthesizer.onReceive(bytes, 0, bytes.length);
-    
- * - * See the example UseMidiKeyboard.java - * - * @author Phil Burk (C) 2016 Mobileer Inc - */ -public class MidiSynthesizer extends MessageParser { - - private MultiChannelSynthesizer multiSynth; - - public MidiSynthesizer(MultiChannelSynthesizer multiSynth) { - this.multiSynth = multiSynth; - } - - @Override - public void controlChange(int channel, int index, int value) { - //System.out.println("controlChange(" + channel + ", " + index + ", " + value + ")"); - double normalized = value * (1.0 / 127.0); - switch (index) { - case MidiConstants.CONTROLLER_MOD_WHEEL: - double vibratoDepth = 0.1 * normalized; - System.out.println( "vibratoDepth = " + vibratoDepth ); - multiSynth.setVibratoDepth(channel, vibratoDepth); - break; - case MidiConstants.CONTROLLER_TIMBRE: - multiSynth.setTimbre(channel, normalized); - break; - case MidiConstants.CONTROLLER_VOLUME: - multiSynth.setVolume(channel, normalized); - break; - case MidiConstants.CONTROLLER_PAN: - // convert to -1 to +1 range - multiSynth.setPan(channel, (normalized * 2.0) - 1.0); - break; - } - } - - @Override - public void registeredParameter(int channel, int index14, int value14) { - switch(index14) { - case MidiConstants.RPN_BEND_RANGE: - int semitones = value14 >> 7; - int cents = value14 & 0x7F; - double bendRange = semitones + (cents * 0.01); - multiSynth.setBendRange(channel, bendRange); - break; - default: - break; - } - } - - @Override - public void programChange(int channel, int program) { - multiSynth.programChange(channel, program); - } - - @Override - public void channelPressure(int channel, int value) { - double normalized = value * (1.0 / 127.0); - multiSynth.setPressure(channel, normalized); - } - - @Override - public void noteOff(int channel, int noteNumber, int velocity) { - multiSynth.noteOff(channel, noteNumber, velocity); - } - - @Override - public void noteOn(int channel, int noteNumber, int velocity) { - multiSynth.noteOn(channel, noteNumber, velocity); - } - - @Override - public void pitchBend(int channel, int bend) { - double offset = (bend - MidiConstants.PITCH_BEND_CENTER) - * (1.0 / (MidiConstants.PITCH_BEND_CENTER)); - multiSynth.setPitchBend(channel, offset); - } - - public void onReceive(byte[] bytes, int i, int length) { - parse(bytes); // TODO - } - -} diff --git a/src/com/jsyn/package.html b/src/com/jsyn/package.html deleted file mode 100644 index cd73832..0000000 --- a/src/com/jsyn/package.html +++ /dev/null @@ -1,17 +0,0 @@ - - - - -JSyn Package - - -JSyn is a music and audio synthesis API for Java. The basic sequence of operations is: -
    -
  • Use the JSyn class to create a synthesizer.
  • -
  • Create unit generators and add them to the synthesizer.
  • -
  • Connect unit generators so that audio signals can flow between them.
  • -
  • Start an output generator. It will pull data from the connected units.
  • -
  • Set port values and queue sample and envelope data to change the sound.
  • -
- - \ No newline at end of file diff --git a/src/com/jsyn/ports/ConnectableInput.java b/src/com/jsyn/ports/ConnectableInput.java deleted file mode 100644 index 3dae876..0000000 --- a/src/com/jsyn/ports/ConnectableInput.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright 2011 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; - -/** - * This interface lets you pass either an input port, or a single part of an input port. - * - * @author Phil Burk (C) 2011 Mobileer Inc - */ -public interface ConnectableInput { - public void connect(ConnectableOutput other); - - public void disconnect(ConnectableOutput other); - - /** - * This is used internally by PortBlockPart to make a connection between specific parts of a - * port. - * - * @return - */ - public PortBlockPart getPortBlockPart(); - - public void pullData(long frameCount, int start, int limit); -} diff --git a/src/com/jsyn/ports/ConnectableOutput.java b/src/com/jsyn/ports/ConnectableOutput.java deleted file mode 100644 index f42a799..0000000 --- a/src/com/jsyn/ports/ConnectableOutput.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * 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; - -public interface ConnectableOutput { - public void connect(ConnectableInput other); - - public void disconnect(ConnectableInput other); -} diff --git a/src/com/jsyn/ports/GettablePort.java b/src/com/jsyn/ports/GettablePort.java deleted file mode 100644 index aabf5ca..0000000 --- a/src/com/jsyn/ports/GettablePort.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * 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; - -public interface GettablePort { - String getName(); - - int getNumParts(); - - double getValue(int partNum); - - Object getUnitGenerator(); -} diff --git a/src/com/jsyn/ports/InputMixingBlockPart.java b/src/com/jsyn/ports/InputMixingBlockPart.java deleted file mode 100644 index 5b54b99..0000000 --- a/src/com/jsyn/ports/InputMixingBlockPart.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * 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 java.io.PrintStream; - -import com.jsyn.Synthesizer; -import com.jsyn.unitgen.UnitGenerator; - -/** - * A UnitInputPort has an array of these, one for each part. - * - * @author Phil Burk 2009 Mobileer Inc - */ - -public class InputMixingBlockPart extends PortBlockPart { - private double[] mixer = new double[Synthesizer.FRAMES_PER_BLOCK]; - private double current; - private UnitInputPort unitInputPort; - - InputMixingBlockPart(UnitInputPort unitInputPort, double defaultValue) { - super(unitInputPort, defaultValue); - this.unitInputPort = unitInputPort; - } - - @Override - public double getValue() { - return current; - } - - @Override - protected void setValue(double value) { - current = value; - super.setValue(value); - } - - @Override - public double[] getValues() { - double[] result; - int numConnections = getConnectionCount(); - // System.out.println("numConnection = " + numConnections + " for " + - // this ); - if (numConnections == 0) { - // No connection so just use our own data. - result = super.getValues(); - } else { - // Mix all of the connected ports. - double[] inputs; - int jCon = 0; - PortBlockPart otherPart; - // Choose value to initialize the mixer array. - if (unitInputPort.isValueAdded()) { - inputs = super.getValues(); // prime mixer with the set() values - jCon = 0; - } else { - otherPart = getConnection(jCon); - inputs = otherPart.getValues(); // prime mixer with first connected - jCon = 1; - } - for (int i = 0; i < mixer.length; i++) { - mixer[i] = inputs[i]; - } - // Now mix in the remaining inputs. - for (; jCon < numConnections; jCon++) { - otherPart = getConnection(jCon); - inputs = otherPart.getValues(); - for (int i = 0; i < mixer.length; i++) { - mixer[i] += inputs[i]; - } - } - result = mixer; - } - current = result[0]; - return result; - } - - private void printIndentation(PrintStream out, int level) { - for (int i = 0; i < level; i++) { - out.print(" "); - } - } - - private String portToString(UnitBlockPort port) { - UnitGenerator ugen = port.getUnitGenerator(); - return ugen.getClass().getSimpleName() + "." + port.getName(); - } - - public void printConnections(PrintStream out, int level) { - for (int i = 0; i < getConnectionCount(); i++) { - PortBlockPart part = getConnection(i); - - printIndentation(out, level); - out.println(portToString(getPort()) + " <--- " + portToString(part.getPort())); - - part.getPort().getUnitGenerator().printConnections(out, level + 1); - } - } -} diff --git a/src/com/jsyn/ports/PortBlockPart.java b/src/com/jsyn/ports/PortBlockPart.java deleted file mode 100644 index ad75211..0000000 --- a/src/com/jsyn/ports/PortBlockPart.java +++ /dev/null @@ -1,205 +0,0 @@ -/* - * 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 java.util.ArrayList; - -import com.jsyn.Synthesizer; -import com.jsyn.engine.SynthesisEngine; -import com.softsynth.shared.time.ScheduledCommand; -import com.softsynth.shared.time.TimeStamp; - -/** - * Part of a multi-part port, for example, the left side of a stereo port. - * - * @author Phil Burk (C) 2009 Mobileer Inc - */ -public class PortBlockPart implements ConnectableOutput, ConnectableInput { - private double[] values = new double[Synthesizer.FRAMES_PER_BLOCK]; - private ArrayList connections = new ArrayList(); - private UnitBlockPort unitBlockPort; - - protected PortBlockPart(UnitBlockPort unitBlockPort, double defaultValue) { - this.unitBlockPort = unitBlockPort; - setValue(defaultValue); - } - - public double[] getValues() { - return values; - } - - public double getValue() { - return values[0]; - } - - public double get() { - return values[0]; - } - - protected void setValue(double value) { - for (int i = 0; i < values.length; i++) { - values[i] = value; - } - } - - protected boolean isConnected() { - return (connections.size() > 0); - } - - private void addConnection(PortBlockPart otherPart) { - // System.out.println("addConnection from " + this + " to " + otherPart - // ); - if (connections.contains(otherPart)) { - System.out.println("addConnection already had connection from " + this + " to " - + otherPart); - } else { - connections.add(otherPart); - } - } - - private void removeConnection(PortBlockPart otherPart) { - // System.out.println("removeConnection from " + this + " to " + - // otherPart ); - connections.remove(otherPart); - } - - private void connectNow(PortBlockPart otherPart) { - addConnection(otherPart); - otherPart.addConnection(this); - } - - private void disconnectNow(PortBlockPart otherPart) { - removeConnection(otherPart); - otherPart.removeConnection(this); - } - - private void disconnectAllNow() { - for (PortBlockPart part : connections) { - part.removeConnection(this); - } - connections.clear(); - } - - public PortBlockPart getConnection(int i) { - return connections.get(i); - } - - public int getConnectionCount() { - return connections.size(); - } - - /** Set all values to the last value. */ - protected void flatten() { - double lastValue = values[values.length - 1]; - for (int i = 0; i < values.length - 1; i++) { - values[i] = lastValue; - } - } - - protected UnitBlockPort getPort() { - return unitBlockPort; - } - - private void checkConnection(PortBlockPart destination) { - SynthesisEngine sourceSynth = unitBlockPort.getSynthesisEngine(); - SynthesisEngine destSynth = destination.unitBlockPort.getSynthesisEngine(); - if ((sourceSynth != destSynth) && (sourceSynth != null) && (destSynth != null)) { - throw new RuntimeException("Connection between units on different synths."); - } - } - - protected void connect(final PortBlockPart destination) { - checkConnection(destination); - unitBlockPort.queueCommand(new ScheduledCommand() { - @Override - public void run() { - connectNow(destination); - } - }); - } - - protected void connect(final PortBlockPart destination, TimeStamp timeStamp) { - unitBlockPort.scheduleCommand(timeStamp, new ScheduledCommand() { - @Override - public void run() { - connectNow(destination); - } - }); - } - - protected void disconnect(final PortBlockPart destination) { - unitBlockPort.queueCommand(new ScheduledCommand() { - @Override - public void run() { - disconnectNow(destination); - } - }); - } - - protected void disconnect(final PortBlockPart destination, TimeStamp timeStamp) { - unitBlockPort.scheduleCommand(timeStamp, new ScheduledCommand() { - @Override - public void run() { - disconnectNow(destination); - } - }); - } - - protected void disconnectAll() { - unitBlockPort.queueCommand(new ScheduledCommand() { - @Override - public void run() { - disconnectAllNow(); - } - }); - } - - @Override - public void connect(ConnectableInput other) { - connect(other.getPortBlockPart()); - } - - @Override - public void connect(ConnectableOutput other) { - other.connect(this); - } - - @Override - public void disconnect(ConnectableOutput other) { - other.disconnect(this); - } - - @Override - public void disconnect(ConnectableInput other) { - disconnect(other.getPortBlockPart()); - } - - /** To implement ConnectableInput */ - @Override - public PortBlockPart getPortBlockPart() { - return this; - } - - @Override - public void pullData(long frameCount, int start, int limit) { - for (int i = 0; i < getConnectionCount(); i++) { - PortBlockPart part = getConnection(i); - part.getPort().getUnitGenerator().pullData(frameCount, start, limit); - } - } - -} diff --git a/src/com/jsyn/ports/QueueDataCommand.java b/src/com/jsyn/ports/QueueDataCommand.java deleted file mode 100644 index c23fbcd..0000000 --- a/src/com/jsyn/ports/QueueDataCommand.java +++ /dev/null @@ -1,170 +0,0 @@ -/* - * 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.softsynth.shared.time.ScheduledCommand; - -/** - * A command that can be used to queue SequentialData to a UnitDataQueuePort. Here is an example of - * queuing data with a callback using this command. - * - *
- * 
- * 	// Queue an envelope with a completion callback.
- * 	QueueDataCommand command = envelopePlayer.dataQueue.createQueueDataCommand( envelope, 0,
- * 			envelope.getNumFrames() );
- * 	// Create an object to be called when the queued data is done.
- * 	TestQueueCallback callback = new TestQueueCallback();
- * 	command.setCallback( callback );
- * 	command.setNumLoops( 2 );
- * 	envelopePlayer.rate.set( 0.2 );
- * 	synth.queueCommand( command );
- *  
- * 
- * - * The callback will be passed QueueDataEvents. - * - *
- * 
- * 	class TestQueueCallback implements UnitDataQueueCallback
- * 	{
- * 		public void started( QueueDataEvent event )
- * 		{
- * 			System.out.println("CALLBACK: Envelope started.");
- * 		}
- *
- * 		public void looped( QueueDataEvent event )
- * 		{
- * 			System.out.println("CALLBACK: Envelope looped.");
- * 		}
- *
- * 		public void finished( QueueDataEvent event )
- * 		{
- * 			System.out.println("CALLBACK: Envelope finished.");
- * 		}
- * 	}
- * 
- * 
- * - * @author Phil Burk 2009 Mobileer Inc - */ -public abstract class QueueDataCommand extends QueueDataEvent implements ScheduledCommand { - - protected SequentialDataCrossfade crossfadeData; - protected SequentialData currentData; - - private static final long serialVersionUID = -1185274459972359536L; - private UnitDataQueueCallback callback; - - public QueueDataCommand(UnitDataQueuePort port, SequentialData sequentialData, int startFrame, - int numFrames) { - super(port); - - if ((startFrame + numFrames) > sequentialData.getNumFrames()) { - throw new IllegalArgumentException("tried to queue past end of data, " + (startFrame + numFrames)); - } else if (startFrame < 0) { - throw new IllegalArgumentException("tried to queue before start of data, " + startFrame); - } - this.sequentialData = sequentialData; - this.currentData = sequentialData; - crossfadeData = new SequentialDataCrossfade(); - this.startFrame = startFrame; - this.numFrames = numFrames; - } - - @Override - public abstract void run(); - - /** - * If true then this item will be skipped if other items are queued after it. This flag allows - * you to queue lots of small pieces of sound without making the queue very long. - * - * @param skipIfOthers - */ - public void setSkipIfOthers(boolean skipIfOthers) { - this.skipIfOthers = skipIfOthers; - } - - /** - * If true then the queue will be cleared and this item will be started immediately. It is - * better to use this flag than to clear the queue from the application because there could be a - * gap before the next item is available. This is most useful when combined with - * setCrossFadeIn(). - * - * @param immediate - */ - public void setImmediate(boolean immediate) { - this.immediate = immediate; - } - - public UnitDataQueueCallback getCallback() { - return callback; - } - - public void setCallback(UnitDataQueueCallback callback) { - this.callback = callback; - } - - public SequentialDataCrossfade getCrossfadeData() { - return crossfadeData; - } - - public void setCrossfadeData(SequentialDataCrossfade crossfadeData) { - this.crossfadeData = crossfadeData; - } - - public SequentialData getCurrentData() { - return currentData; - } - - public void setCurrentData(SequentialData currentData) { - this.currentData = currentData; - } - - /** - * Stop the unit that contains this port after this command has finished. - * - * @param autoStop - */ - public void setAutoStop(boolean autoStop) { - this.autoStop = autoStop; - } - - /** - * Set how many time the block should be repeated after the first time. For example, if you set - * numLoops to zero the block will only be played once. If you set numLoops to one the block - * will be played twice. - * - * @param numLoops number of times to loop back - */ - public void setNumLoops(int numLoops) { - this.numLoops = numLoops; - } - - /** - * Number of frames to cross fade from the previous block to this block. This can be used to - * avoid pops when making abrupt transitions. There must be frames available after the end of - * the previous block to use for crossfading. The crossfade is linear. - * - * @param size - */ - public void setCrossFadeIn(int size) { - this.crossFadeIn = size; - } - -} diff --git a/src/com/jsyn/ports/QueueDataEvent.java b/src/com/jsyn/ports/QueueDataEvent.java deleted file mode 100644 index 2b93fab..0000000 --- a/src/com/jsyn/ports/QueueDataEvent.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * 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 java.util.EventObject; - -import com.jsyn.data.SequentialData; - -/** - * An event that is passed to a UnitDataQueueCallback when the element in the queue is played.. - * - * @author Phil Burk 2009 Mobileer Inc - */ -public class QueueDataEvent extends EventObject { - private static final long serialVersionUID = 176846633064538053L; - protected SequentialData sequentialData; - protected int startFrame; - protected int numFrames; - protected int numLoops; - protected int loopsLeft; - protected int crossFadeIn; - protected boolean skipIfOthers; - protected boolean autoStop; - protected boolean immediate; - - public QueueDataEvent(Object arg0) { - super(arg0); - } - - public boolean isSkipIfOthers() { - return skipIfOthers; - } - - public boolean isImmediate() { - return immediate; - } - - public SequentialData getSequentialData() { - return sequentialData; - } - - public int getCrossFadeIn() { - return crossFadeIn; - } - - public int getStartFrame() { - return startFrame; - } - - public int getNumFrames() { - return numFrames; - } - - public int getNumLoops() { - return numLoops; - } - - public int getLoopsLeft() { - return loopsLeft; - } - - public boolean isAutoStop() { - return autoStop; - } - -} diff --git a/src/com/jsyn/ports/SequentialDataCrossfade.java b/src/com/jsyn/ports/SequentialDataCrossfade.java deleted file mode 100644 index 25e1fd9..0000000 --- a/src/com/jsyn/ports/SequentialDataCrossfade.java +++ /dev/null @@ -1,139 +0,0 @@ -/* - * 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: - * - *
- * SequentialDataCrossfade xfade = new SequentialDataCrossfade(sample, (50000 + 30000), 5000, sample,
- *         50000, 30000);
- * 
- * - * 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() - *
    - *
  1. Crossfade region with size crossFadeFrames. It fades smoothly from source to target.
  2. - *
  3. Steady region that is simply the target values with size (numFrames-crossFadeFrames).
  4. - *
- * - *
- *     "Crossfade Region"      "Steady Region"
- * |-- source fading out --|
- * |-- target fading in  --|-- remainder of target at original volume --|
- * 
- * - * @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(); - } - -} diff --git a/src/com/jsyn/ports/SettablePort.java b/src/com/jsyn/ports/SettablePort.java deleted file mode 100644 index e0db05c..0000000 --- a/src/com/jsyn/ports/SettablePort.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * 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.softsynth.shared.time.TimeStamp; - -/** - * Port whose parts can be set. - * - * @author Phil Burk (C) 2009 Mobileer Inc - */ -public interface SettablePort extends GettablePort { - void set(int partNum, double value, TimeStamp timeStamp); -} diff --git a/src/com/jsyn/ports/UnitBlockPort.java b/src/com/jsyn/ports/UnitBlockPort.java deleted file mode 100644 index d7fc82f..0000000 --- a/src/com/jsyn/ports/UnitBlockPort.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * 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; - -/** - * A port that contains multiple parts with blocks of data. - * - * @author Phil Burk 2009 Mobileer Inc - */ -public class UnitBlockPort extends UnitPort { - PortBlockPart[] parts; - - public UnitBlockPort(int numParts, String name, double defaultValue) { - super(name); - makeParts(numParts, defaultValue); - } - - public UnitBlockPort(String name) { - this(1, name, 0.0); - } - - protected void makeParts(int numParts, double defaultValue) { - parts = new PortBlockPart[numParts]; - for (int i = 0; i < numParts; i++) { - parts[i] = new PortBlockPart(this, defaultValue); - } - } - - @Override - public int getNumParts() { - return parts.length; - } - - /** - * Convenience call to get(0). - * - * @return value of 0th part as set - */ - public double get() { - return get(0); - } - - public double getValue() { - return getValue(0); - } - - /** - * This is used inside UnitGenerators to get the current values for a port. It works regardless - * of whether the port is connected or not. - * - * @return - */ - public double[] getValues() { - return parts[0].getValues(); - } - - /** Only for use in the audio thread when implementing UnitGenerators. */ - public double[] getValues(int partNum) { - return parts[partNum].getValues(); - } - - /** Get the immediate current value of the port. */ - public double getValue(int partNum) { - return parts[partNum].getValue(); - } - - public double get(int partNum) { - return parts[partNum].get(); - } - - /** Only for use in the audio thread when implementing UnitGenerators. */ - protected void setValueInternal(int partNum, double value) { - parts[partNum].setValue(value); - } - - /** Only for use in the audio thread when implementing UnitGenerators. */ - public void setValueInternal(double value) { - setValueInternal(0, value); - } - - public boolean isConnected() { - return isConnected(0); - } - - public boolean isConnected(int partNum) { - return parts[partNum].isConnected(); - } - - public void disconnectAll(int partNum) { - parts[partNum].disconnectAll(); - } - - public void disconnectAll() { - disconnectAll(0); - } -} diff --git a/src/com/jsyn/ports/UnitDataQueueCallback.java b/src/com/jsyn/ports/UnitDataQueueCallback.java deleted file mode 100644 index dca4adc..0000000 --- a/src/com/jsyn/ports/UnitDataQueueCallback.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2011 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; - -/** - * This is called when a block of data that is queued to a UnitDataQueuePort starts, loops, or - * finishes. - * - * @author Phil Burk (C) 2011 Mobileer Inc - */ -public interface UnitDataQueueCallback { - public void started(QueueDataEvent event); - - public void looped(QueueDataEvent event); - - public void finished(QueueDataEvent event); -} diff --git a/src/com/jsyn/ports/UnitDataQueuePort.java b/src/com/jsyn/ports/UnitDataQueuePort.java deleted file mode 100644 index 5487589..0000000 --- a/src/com/jsyn/ports/UnitDataQueuePort.java +++ /dev/null @@ -1,466 +0,0 @@ -/* - * 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 java.util.LinkedList; - -import com.jsyn.data.SequentialData; -import com.jsyn.exceptions.ChannelMismatchException; -import com.softsynth.shared.time.ScheduledCommand; -import com.softsynth.shared.time.TimeStamp; - -/** - * Queue for SequentialData, samples or envelopes - * - * @author Phil Burk (C) 2009 Mobileer Inc - */ -public class UnitDataQueuePort extends UnitPort { - private final LinkedList blocks = new LinkedList(); - private QueueDataCommand currentBlock; - private int frameIndex; - private int numChannels = 1; - private double normalizedRate; - private long framesMoved; - private boolean autoStopPending; - private boolean targetValid; - private QueueDataCommand finishingBlock; - private QueueDataCommand loopingBlock; - public static final int LOOP_IF_LAST = -1; - - public UnitDataQueuePort(String name) { - super(name); - } - - /** Hold a reference to part of a sample. */ - @SuppressWarnings("serial") - private class QueuedBlock extends QueueDataCommand { - - public QueuedBlock(SequentialData queueableData, int startFrame, int numFrames) { - super(UnitDataQueuePort.this, queueableData, startFrame, numFrames); - } - - @Override - public void run() { - synchronized (blocks) { - // Remove last block if it can be skipped. - if (blocks.size() > 0) { - QueueDataEvent lastBlock = blocks.getLast(); - if (lastBlock.isSkipIfOthers()) { - blocks.removeLast(); - } - } - - // If we are crossfading then figure out where to crossfade - // from. - if (getCrossFadeIn() > 0) { - if (isImmediate()) { - // Queue will be cleared so fade in from current. - if (currentBlock != null) { - setupCrossFade(currentBlock, frameIndex, this); - } - // else nothing is playing so don't crossfade. - } else { - QueueDataCommand endBlock = getEndBlock(); - if (endBlock != null) { - setupCrossFade(endBlock, - endBlock.getStartFrame() + endBlock.getNumFrames(), this); - } - } - } - - if (isImmediate()) { - clearQueue(); - } - - blocks.add(this); - } - } - } - - // FIXME - determine crossfade on any transition between blocks or when looping back. - - protected void setupCrossFade(QueueDataCommand sourceCommand, int sourceStartIndex, - QueueDataCommand targetCommand) { - int crossFrames = targetCommand.getCrossFadeIn(); - SequentialData sourceData = sourceCommand.getCurrentData(); - SequentialData targetData = targetCommand.getCurrentData(); - int remainingSource = sourceData.getNumFrames() - sourceStartIndex; - // clip to end of source - if (crossFrames > remainingSource) - crossFrames = remainingSource; - if (crossFrames > 0) { - // The SequentialDataCrossfade should continue to the end of the target - // so that we can crossfade from it to the target. - int remainingTarget = targetData.getNumFrames() - targetCommand.getStartFrame(); - targetCommand.crossfadeData.setup(sourceData, sourceStartIndex, crossFrames, - targetData, targetCommand.getStartFrame(), remainingTarget); - targetCommand.currentData = targetCommand.crossfadeData; - targetCommand.startFrame = 0; - } - } - - public QueueDataCommand createQueueDataCommand(SequentialData queueableData) { - return createQueueDataCommand(queueableData, 0, queueableData.getNumFrames()); - } - - public QueueDataCommand createQueueDataCommand(SequentialData queueableData, int startFrame, - int numFrames) { - if (queueableData.getChannelsPerFrame() != UnitDataQueuePort.this.numChannels) { - throw new ChannelMismatchException("Tried to queue " - + queueableData.getChannelsPerFrame() + " channel data to a " + numChannels - + " channel port."); - } - return new QueuedBlock(queueableData, startFrame, numFrames); - } - - public QueueDataCommand getEndBlock() { - if (blocks.size() > 0) { - return blocks.getLast(); - } else if (currentBlock != null) { - return currentBlock; - } else { - return null; - } - } - - public void setCurrentBlock(QueueDataCommand currentBlock) { - this.currentBlock = currentBlock; - } - - public void firePendingCallbacks() { - if (loopingBlock != null) { - if (loopingBlock.getCallback() != null) { - loopingBlock.getCallback().looped(currentBlock); - } - loopingBlock = null; - } - if (finishingBlock != null) { - if (finishingBlock.getCallback() != null) { - finishingBlock.getCallback().finished(currentBlock); // FIXME - Should this pass - // finishingBlock?! - } - finishingBlock = null; - } - } - - public boolean hasMore() { - return (currentBlock != null) || (blocks.size() > 0); - } - - private void checkBlock() { - if (currentBlock == null) { - synchronized (blocks) { - setCurrentBlock(blocks.remove()); - frameIndex = currentBlock.getStartFrame(); - currentBlock.loopsLeft = currentBlock.getNumLoops(); - if (currentBlock.getCallback() != null) { - currentBlock.getCallback().started(currentBlock); - } - } - } - } - - private void advanceFrameIndex() { - frameIndex += 1; - framesMoved += 1; - // Are we done with this block? - if (frameIndex >= (currentBlock.getStartFrame() + currentBlock.getNumFrames())) { - // Should we loop on this block based on a counter? - if (currentBlock.loopsLeft > 0) { - currentBlock.loopsLeft -= 1; - loopToStart(); - } - // Should we loop forever on this block? - else if ((blocks.size() == 0) && (currentBlock.loopsLeft < 0)) { - loopToStart(); - } - // We are done. - else { - if (currentBlock.isAutoStop()) { - autoStopPending = true; - } - finishingBlock = currentBlock; - setCurrentBlock(null); - // System.out.println("advanceFrameIndex: currentBlock set null"); - } - } - } - - private void loopToStart() { - if (currentBlock.getCrossFadeIn() > 0) { - setupCrossFade(currentBlock, frameIndex, currentBlock); - } - frameIndex = currentBlock.getStartFrame(); - loopingBlock = currentBlock; - } - - public double getNormalizedRate() { - return normalizedRate; - } - - public double readCurrentChannelDouble(int channelIndex) { - return currentBlock.currentData.readDouble((frameIndex * numChannels) + channelIndex); - } - - public void writeCurrentChannelDouble(int channelIndex, double value) { - currentBlock.currentData.writeDouble((frameIndex * numChannels) + channelIndex, value); - } - - public void beginFrame(double synthesisPeriod) { - checkBlock(); - normalizedRate = currentBlock.currentData.getRateScaler(frameIndex, synthesisPeriod); - } - - public void endFrame() { - advanceFrameIndex(); - targetValid = true; - } - - public double readNextMonoDouble(double synthesisPeriod) { - beginFrame(synthesisPeriod); - double value = currentBlock.currentData.readDouble(frameIndex); - endFrame(); - return value; - } - - /** Write directly to the port queue. This is only called by unit tests! */ - protected void addQueuedBlock(QueueDataEvent block) { - blocks.add((QueuedBlock) block); - } - - /** Clear the queue. Internal use only. */ - protected void clearQueue() { - synchronized (blocks) { - blocks.clear(); - setCurrentBlock(null); - targetValid = false; - autoStopPending = false; - } - } - - class ClearQueueCommand implements ScheduledCommand { - @Override - public void run() { - clearQueue(); - } - } - - /** Queue the data to the port at a future time. */ - public void queue(SequentialData queueableData, int startFrame, int numFrames, - TimeStamp timeStamp) { - QueueDataCommand command = createQueueDataCommand(queueableData, startFrame, numFrames); - scheduleCommand(timeStamp, command); - } - - /** - * Queue the data to the port at a future time. Command will clear the queue before executing. - */ - public void queueImmediate(SequentialData queueableData, int startFrame, int numFrames, - TimeStamp timeStamp) { - QueueDataCommand command = createQueueDataCommand(queueableData, startFrame, numFrames); - command.setImmediate(true); - scheduleCommand(timeStamp, command); - } - - /** Queue the data to the port at a future time. */ - public void queueLoop(SequentialData queueableData, int startFrame, int numFrames, - TimeStamp timeStamp) { - queueLoop(queueableData, startFrame, numFrames, LOOP_IF_LAST, timeStamp); - } - - /** - * Queue the data to the port at a future time with a specified number of loops. - */ - public void queueLoop(SequentialData queueableData, int startFrame, int numFrames, - int numLoops, TimeStamp timeStamp) { - QueueDataCommand command = createQueueDataCommand(queueableData, startFrame, numFrames); - command.setNumLoops(numLoops); - scheduleCommand(timeStamp, command); - } - - /** Queue the entire data object for looping. */ - public void queueLoop(SequentialData queueableData) { - queueLoop(queueableData, 0, queueableData.getNumFrames()); - } - - /** Queue the data to the port for immediate use. */ - public void queueLoop(SequentialData queueableData, int startFrame, int numFrames) { - queueLoop(queueableData, startFrame, numFrames, LOOP_IF_LAST); - } - - /** - * Queue the data to the port for immediate use with a specified number of loops. - */ - public void queueLoop(SequentialData queueableData, int startFrame, int numFrames, int numLoops) { - QueueDataCommand command = createQueueDataCommand(queueableData, startFrame, numFrames); - command.setNumLoops(numLoops); - queueCommand(command); - } - - /** - * Queue the data to the port at a future time. Request that the unit stop when this block is - * finished. - */ - public void queueStop(SequentialData queueableData, int startFrame, int numFrames, - TimeStamp timeStamp) { - QueueDataCommand command = createQueueDataCommand(queueableData, startFrame, numFrames); - command.setAutoStop(true); - scheduleCommand(timeStamp, command); - } - - /** Queue the data to the port through the command queue ASAP. */ - public void queue(SequentialData queueableData, int startFrame, int numFrames) { - QueueDataCommand command = createQueueDataCommand(queueableData, startFrame, numFrames); - queueCommand(command); - } - - /** - * Queue entire amount of data with no options. - * - * @param queueableData - */ - public void queue(SequentialData queueableData) { - queue(queueableData, 0, queueableData.getNumFrames()); - } - - /** Schedule queueOn now! */ - public void queueOn(SequentialData queueableData) { - queueOn(queueableData, getSynthesisEngine().createTimeStamp()); - } - - /** Schedule queueOff now! */ - public void queueOff(SequentialData queueableData) { - queueOff(queueableData, false); - } - - /** Schedule queueOff now! */ - public void queueOff(SequentialData queueableData, boolean ifStop) { - queueOff(queueableData, ifStop, getSynthesisEngine().createTimeStamp()); - } - - /** - * Convenience method that will queue the attack portion of a channelData and the sustain loop - * if it exists. This could be used to implement a NoteOn method. - */ - public void queueOn(SequentialData queueableData, TimeStamp timeStamp) { - - if (queueableData.getSustainBegin() < 0) { - // no sustain loop, handle release - if (queueableData.getReleaseBegin() < 0) { - // No loops - queueImmediate(queueableData, 0, queueableData.getNumFrames(), timeStamp); - } else { - queueImmediate(queueableData, 0, queueableData.getReleaseEnd(), timeStamp); - int size = queueableData.getReleaseEnd() - queueableData.getReleaseBegin(); - queueLoop(queueableData, queueableData.getReleaseBegin(), size, timeStamp); - } - } else { - // yes sustain loop - if (queueableData.getSustainEnd() > 0) { - int frontSize = queueableData.getSustainBegin(); - int loopSize = queueableData.getSustainEnd() - queueableData.getSustainBegin(); - // Is there an initial portion before the sustain loop? - if (frontSize > 0) { - queueImmediate(queueableData, 0, frontSize, timeStamp); - } - loopSize = queueableData.getSustainEnd() - queueableData.getSustainBegin(); - if (loopSize > 0) { - queueLoop(queueableData, queueableData.getSustainBegin(), loopSize, timeStamp); - } - } - - } - } - - /** - * Convenience method that will queue the decay portion of a SequentialData object, or the gap - * and release loop portions if they exist. This could be used to implement a NoteOff method. - * - * @param ifStop Will setAutostop(true) if release portion queued without a release loop. This will - * stop execution of the unit. - */ - public void queueOff(SequentialData queueableData, boolean ifStop, TimeStamp timeStamp) { - if (queueableData.getSustainBegin() >= 0) /* Sustain loop? */ - { - int relSize = queueableData.getReleaseEnd() - queueableData.getReleaseBegin(); - if (queueableData.getReleaseBegin() < 0) { /* Sustain loop, no release loop. */ - int susEnd = queueableData.getSustainEnd(); - int size = queueableData.getNumFrames() - susEnd; - // System.out.println("queueOff: size = " + size ); - if (size <= 0) { - // always queue something so that we can stop the loop - // 20001117 - size = 1; - susEnd = queueableData.getNumFrames() - 1; - } - if (ifStop) { - queueStop(queueableData, susEnd, size, timeStamp); - } else { - queue(queueableData, susEnd, size, timeStamp); - } - } else if (queueableData.getReleaseBegin() > queueableData.getSustainEnd()) { - // Queue gap between sustain and release loop. - queue(queueableData, queueableData.getSustainEnd(), queueableData.getReleaseEnd() - - queueableData.getSustainEnd(), timeStamp); - if (relSize > 0) - queueLoop(queueableData, queueableData.getReleaseBegin(), relSize, timeStamp); - } else if (relSize > 0) { - // No gap between sustain and release. - queueLoop(queueableData, queueableData.getReleaseBegin(), relSize, timeStamp); - } - } - /* If no sustain loop, then nothing to do. */ - } - - public void clear(TimeStamp timeStamp) { - ScheduledCommand command = new ClearQueueCommand(); - scheduleCommand(timeStamp, command); - } - - public void clear() { - ScheduledCommand command = new ClearQueueCommand(); - queueCommand(command); - } - - public void writeNextDouble(double value) { - checkBlock(); - currentBlock.currentData.writeDouble(frameIndex, value); - advanceFrameIndex(); - } - - public long getFrameCount() { - return framesMoved; - } - - public boolean testAndClearAutoStop() { - boolean temp = autoStopPending; - autoStopPending = false; - return temp; - } - - public boolean isTargetValid() { - return targetValid; - } - - public void setNumChannels(int numChannels) { - this.numChannels = numChannels; - } - - public int getNumChannels() { - return numChannels; - } -} diff --git a/src/com/jsyn/ports/UnitFunctionPort.java b/src/com/jsyn/ports/UnitFunctionPort.java deleted file mode 100644 index e45241a..0000000 --- a/src/com/jsyn/ports/UnitFunctionPort.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * 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.Function; - -/** - * Port for holding a Function object. - * - * @author Phil Burk (C) 2009 Mobileer Inc - */ -public class UnitFunctionPort extends UnitPort { - private static NullFunction nullFunction = new NullFunction(); - private Function function = nullFunction; - - private static class NullFunction implements Function { - @Override - public double evaluate(double input) { - return 0.0; - } - } - - public UnitFunctionPort(String name) { - super(name); - } - - public void set(Function function) { - this.function = function; - } - - public Function get() { - return function; - } -} diff --git a/src/com/jsyn/ports/UnitGatePort.java b/src/com/jsyn/ports/UnitGatePort.java deleted file mode 100644 index 700aef8..0000000 --- a/src/com/jsyn/ports/UnitGatePort.java +++ /dev/null @@ -1,158 +0,0 @@ -/* - * 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.unitgen.UnitGenerator; -import com.softsynth.shared.time.ScheduledCommand; -import com.softsynth.shared.time.TimeStamp; - -public class UnitGatePort extends UnitInputPort { - private boolean autoDisableEnabled = false; - private boolean triggered = false; - private boolean off = true; - private UnitGenerator gatedUnit; - public static final double THRESHOLD = 0.01; - - public UnitGatePort(String name) { - super(name); - } - - public void on() { - setOn(true); - } - - public void off() { - setOn(false); - } - - public void off(TimeStamp timeStamp) { - setOn(false, timeStamp); - } - - public void on(TimeStamp timeStamp) { - setOn(true, timeStamp); - } - - private void setOn(final boolean on) { - queueCommand(new ScheduledCommand() { - @Override - public void run() { - setOnInternal(on); - } - }); - } - - private void setOn(final boolean on, TimeStamp timeStamp) { - scheduleCommand(timeStamp, new ScheduledCommand() { - @Override - public void run() { - setOnInternal(on); - } - }); - } - - private void setOnInternal(boolean on) { - if (on) { - triggerInternal(); - } - setValueInternal(on ? 1.0 : 0.0); - } - - private void triggerInternal() { - getGatedUnit().setEnabled(true); - triggered = true; - } - - public void trigger() { - queueCommand(new ScheduledCommand() { - @Override - public void run() { - triggerInternal(); - } - }); - } - - public void trigger(TimeStamp timeStamp) { - scheduleCommand(timeStamp, new ScheduledCommand() { - @Override - public void run() { - triggerInternal(); - } - }); - } - - /** - * This is called by UnitGenerators. It sets the off value that can be tested using isOff(). - * - * @param i - * @return true if triggered by a positive edge. - */ - public boolean checkGate(int i) { - double[] inputs = getValues(); - boolean result = triggered; - triggered = false; - if (off) { - if (inputs[i] >= THRESHOLD) { - result = true; - off = false; - } - } else { - if (inputs[i] < THRESHOLD) { - off = true; - } - } - return result; - } - - public boolean isOff() { - return off; - } - - public boolean isAutoDisableEnabled() { - return autoDisableEnabled; - } - - /** - * Request the containing UnitGenerator be disabled when checkAutoDisabled() is called. This can - * be used to reduce CPU load. - * - * @param autoDisableEnabled - */ - public void setAutoDisableEnabled(boolean autoDisableEnabled) { - this.autoDisableEnabled = autoDisableEnabled; - } - - /** - * Called by UnitGenerator when an envelope reaches the end of its contour. - */ - public void checkAutoDisable() { - if (autoDisableEnabled) { - getGatedUnit().setEnabled(false); - } - } - - private UnitGenerator getGatedUnit() { - return (gatedUnit == null) ? getUnitGenerator() : gatedUnit; - } - - public void setupAutoDisable(UnitGenerator unit) { - gatedUnit = unit; - setAutoDisableEnabled(true); - // Start off disabled so we don't immediately swamp the CPU. - gatedUnit.setEnabled(false); - } -} diff --git a/src/com/jsyn/ports/UnitInputPort.java b/src/com/jsyn/ports/UnitInputPort.java deleted file mode 100644 index 3eda1f6..0000000 --- a/src/com/jsyn/ports/UnitInputPort.java +++ /dev/null @@ -1,254 +0,0 @@ -/* - * 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 java.io.PrintStream; - -import com.softsynth.shared.time.ScheduledCommand; -import com.softsynth.shared.time.TimeStamp; - -/** - * A port that is used to pass values into a UnitGenerator. - * - * @author Phil Burk 2009 Mobileer Inc - */ -public class UnitInputPort extends UnitBlockPort implements ConnectableInput, SettablePort { - private double minimum = 0.0; - private double maximum = 1.0; - private double defaultValue = 0.0; - private double[] setValues; - private boolean valueAdded = false; - - /** - * @param numParts typically 1, use 2 for stereo ports - * @param name name that may be used in GUIs - * @param defaultValue - */ - public UnitInputPort(int numParts, String name, double defaultValue) { - super(numParts, name, defaultValue); - setDefault(defaultValue); - setValues = new double[numParts]; - for (int i = 0; i < numParts; i++) { - setValues[i] = defaultValue; - } - } - - public UnitInputPort(String name, double defaultValue) { - this(1, name, defaultValue); - } - - public UnitInputPort(String name) { - this(1, name, 0.0); - } - - public UnitInputPort(int numParts, String name) { - this(numParts, name, 0.0); - } - - @Override - protected void makeParts(int numParts, double defaultValue) { - parts = new InputMixingBlockPart[numParts]; - for (int i = 0; i < numParts; i++) { - parts[i] = new InputMixingBlockPart(this, defaultValue); - } - } - - /** - * This is used internally by the SynthesisEngine to execute units based on their connections. - * - * @param frameCount - * @param start - * @param limit - */ - @Override - public void pullData(long frameCount, int start, int limit) { - for (PortBlockPart part : parts) { - ((InputMixingBlockPart) part).pullData(frameCount, start, limit); - } - } - - @Override - protected void setValueInternal(int partNum, double value) { - super.setValueInternal(partNum, value); - setValues[partNum] = value; - } - - public void set(double value) { - set(0, value); - } - - public void set(final int partNum, final double value) { - // Trigger exception now if out of range. - setValues[partNum] = value; - queueCommand(new ScheduledCommand() { - @Override - public void run() { - setValueInternal(partNum, value); - } - }); - } - - public void set(double value, TimeStamp time) { - set(0, value, time); - } - - public void set(double value, double time) { - set(0, value, time); - } - - public void set(int partNum, double value, double time) { - set(partNum, value, new TimeStamp(time)); - } - - @Override - public void set(final int partNum, final double value, TimeStamp timeStamp) { - // Trigger exception now if out of range. - getValue(partNum); - scheduleCommand(timeStamp, new ScheduledCommand() { - @Override - public void run() { - setValueInternal(partNum, value); - } - }); - } - - /** - * Value of a port based on the set() calls. Not affected by connected ports. - * - * @param partNum - * @return value as set - */ - @Override - public double get(int partNum) { - return setValues[partNum]; - } - - public double getMaximum() { - return maximum; - } - - /** - * The minimum and maximum are only used when setting up knobs or other control systems. The - * internal values are not clipped to this range. - * - * @param maximum - */ - public void setMaximum(double maximum) { - this.maximum = maximum; - } - - public double getMinimum() { - return minimum; - } - - public void setMinimum(double minimum) { - this.minimum = minimum; - } - - public double getDefault() { - return defaultValue; - } - - public void setDefault(double defaultValue) { - this.defaultValue = defaultValue; - } - - /** - * Convenience function for setting limits on a port. These limits are recommended values when - * setting up a GUI. It is possible to set a port to a value outside these limits. - * - * @param minimum - * @param value default value, will be clipped to min/max - * @param maximum - */ - public void setup(double minimum, double value, double maximum) { - setMinimum(minimum); - setMaximum(maximum); - setDefault(value); - set(value); - } - - // Grab min, max, default from another port. - public void setup(UnitInputPort other) { - setup(other.getMinimum(), other.getDefault(), other.getMaximum()); - } - - public boolean isValueAdded() { - return valueAdded; - } - - /** - * If set false then the set() value will be ignored when other ports are connected to this port. - * The sum of the connected port values will be used instead. - * - * If set true then the set() value will be added to the sum of the connected port values. - * This is useful when you want to modulate the set value. - * - * The default is false. - * - * @param valueAdded - */ - public void setValueAdded(boolean valueAdded) { - this.valueAdded = valueAdded; - } - - public void connect(int thisPartNum, UnitOutputPort otherPort, int otherPartNum, - TimeStamp timeStamp) { - otherPort.connect(otherPartNum, this, thisPartNum, timeStamp); - } - - /** Connect an input to an output port. */ - public void connect(int thisPartNum, UnitOutputPort otherPort, int otherPartNum) { - // Typically connections are made from output to input because it is - // more intuitive. - otherPort.connect(otherPartNum, this, thisPartNum); - } - - public void connect(UnitOutputPort otherPort) { - connect(0, otherPort, 0); - } - - @Override - public void connect(ConnectableOutput other) { - other.connect(this); - } - - public void disconnect(int thisPartNum, UnitOutputPort otherPort, int otherPartNum) { - otherPort.disconnect(otherPartNum, this, thisPartNum); - } - - @Override - public PortBlockPart getPortBlockPart() { - return parts[0]; - } - - public ConnectableInput getConnectablePart(int i) { - return parts[i]; - } - - @Override - public void disconnect(ConnectableOutput other) { - other.disconnect(this); - } - - public void printConnections(PrintStream out, int level) { - for (PortBlockPart part : parts) { - ((InputMixingBlockPart) part).printConnections(out, level); - } - } - -} diff --git a/src/com/jsyn/ports/UnitOutputPort.java b/src/com/jsyn/ports/UnitOutputPort.java deleted file mode 100644 index 6fcd758..0000000 --- a/src/com/jsyn/ports/UnitOutputPort.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * 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.unitgen.UnitSink; -import com.softsynth.shared.time.TimeStamp; - -/** - * Units write to their output port blocks. Other multiple connected input ports read from them. - * - * @author Phil Burk (C) 2009 Mobileer Inc - */ - -public class UnitOutputPort extends UnitBlockPort implements ConnectableOutput, GettablePort { - public UnitOutputPort() { - this("Output"); - } - - public UnitOutputPort(String name) { - this(1, name, 0.0); - } - - public UnitOutputPort(int numParts, String name) { - this(numParts, name, 0.0); - } - - public UnitOutputPort(int numParts, String name, double defaultValue) { - super(numParts, name, defaultValue); - } - - public void flatten() { - for (PortBlockPart part : parts) { - part.flatten(); - } - } - - public void connect(int thisPartNum, UnitInputPort otherPort, int otherPartNum) { - PortBlockPart source = parts[thisPartNum]; - PortBlockPart destination = otherPort.parts[otherPartNum]; - source.connect(destination); - } - - public void connect(int thisPartNum, UnitInputPort otherPort, int otherPartNum, - TimeStamp timeStamp) { - PortBlockPart source = parts[thisPartNum]; - PortBlockPart destination = otherPort.parts[otherPartNum]; - source.connect(destination, timeStamp); - } - - public void connect(UnitInputPort input) { - connect(0, input, 0); - } - - @Override - public void connect(ConnectableInput input) { - parts[0].connect(input); - } - - public void connect(UnitSink sink) { - connect(0, sink.getInput(), 0); - } - - public void disconnect(int thisPartNum, UnitInputPort otherPort, int otherPartNum) { - PortBlockPart source = parts[thisPartNum]; - PortBlockPart destination = otherPort.parts[otherPartNum]; - source.disconnect(destination); - } - - public void disconnect(int thisPartNum, UnitInputPort otherPort, int otherPartNum, - TimeStamp timeStamp) { - PortBlockPart source = parts[thisPartNum]; - PortBlockPart destination = otherPort.parts[otherPartNum]; - source.disconnect(destination, timeStamp); - } - - public void disconnect(UnitInputPort otherPort) { - disconnect(0, otherPort, 0); - } - - @Override - public void disconnect(ConnectableInput input) { - parts[0].disconnect(input); - } - - public ConnectableOutput getConnectablePart(int i) { - return parts[i]; - } - -} diff --git a/src/com/jsyn/ports/UnitPort.java b/src/com/jsyn/ports/UnitPort.java deleted file mode 100644 index a652e68..0000000 --- a/src/com/jsyn/ports/UnitPort.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * 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.engine.SynthesisEngine; -import com.jsyn.unitgen.UnitGenerator; -import com.softsynth.shared.time.ScheduledCommand; -import com.softsynth.shared.time.TimeStamp; - -/** - * Basic audio port for JSyn unit generators. - * - * @author Phil Burk (C) 2009 Mobileer Inc - */ -public class UnitPort { - private String name; - private UnitGenerator unit; - - public UnitPort(String name) { - this.name = name; - } - - public void setName(String name) { - this.name = name; - } - - public String getName() { - return name; - } - - public void setUnitGenerator(UnitGenerator unit) { - // If a port is in a circuit then we want to just use the lower level - // unit that instantiated the circuit. - if (this.unit == null) { - this.unit = unit; - } - } - - public UnitGenerator getUnitGenerator() { - return unit; - } - - SynthesisEngine getSynthesisEngine() { - if (unit == null) { - return null; - } - return unit.getSynthesisEngine(); - } - - public int getNumParts() { - return 1; - } - - public void scheduleCommand(TimeStamp timeStamp, ScheduledCommand scheduledCommand) { - if (getSynthesisEngine() == null) { - scheduledCommand.run(); - } else { - getSynthesisEngine().scheduleCommand(timeStamp, scheduledCommand); - } - } - - public void queueCommand(ScheduledCommand scheduledCommand) { - if (getSynthesisEngine() == null) { - scheduledCommand.run(); - } else { - getSynthesisEngine().scheduleCommand(getSynthesisEngine().createTimeStamp(), - scheduledCommand); - } - } - -} diff --git a/src/com/jsyn/ports/UnitSpectralInputPort.java b/src/com/jsyn/ports/UnitSpectralInputPort.java deleted file mode 100644 index bdf0ff5..0000000 --- a/src/com/jsyn/ports/UnitSpectralInputPort.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * 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.Spectrum; - -public class UnitSpectralInputPort extends UnitPort implements ConnectableInput { - private UnitSpectralOutputPort other; - - private Spectrum spectrum; - - public UnitSpectralInputPort() { - this("Output"); - } - - public UnitSpectralInputPort(String name) { - super(name); - } - - public void setSpectrum(Spectrum spectrum) { - this.spectrum = spectrum; - } - - public Spectrum getSpectrum() { - if (other == null) { - return spectrum; - } else { - return other.getSpectrum(); - } - } - - @Override - public void connect(ConnectableOutput other) { - if (other instanceof UnitSpectralOutputPort) { - this.other = (UnitSpectralOutputPort) other; - } else { - throw new RuntimeException( - "Can only connect UnitSpectralOutputPort to UnitSpectralInputPort!"); - } - } - - @Override - public void disconnect(ConnectableOutput other) { - if (this.other == other) { - this.other = null; - } - } - - @Override - public PortBlockPart getPortBlockPart() { - return null; - } - - @Override - public void pullData(long frameCount, int start, int limit) { - if (other != null) { - other.getUnitGenerator().pullData(frameCount, start, limit); - } - } - - public boolean isAvailable() { - if (other != null) { - return other.isAvailable(); - } else { - return (spectrum != null); - } - } - -} diff --git a/src/com/jsyn/ports/UnitSpectralOutputPort.java b/src/com/jsyn/ports/UnitSpectralOutputPort.java deleted file mode 100644 index 51633ce..0000000 --- a/src/com/jsyn/ports/UnitSpectralOutputPort.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * 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.Spectrum; - -public class UnitSpectralOutputPort extends UnitPort implements ConnectableOutput { - private Spectrum spectrum; - private boolean available; - - public UnitSpectralOutputPort() { - this("Output"); - } - - public UnitSpectralOutputPort(int size) { - this("Output", size); - } - - public UnitSpectralOutputPort(String name) { - super(name); - spectrum = new Spectrum(); - } - - public UnitSpectralOutputPort(String name, int size) { - super(name); - spectrum = new Spectrum(size); - } - - public void setSize(int size) { - spectrum.setSize(size); - } - - public Spectrum getSpectrum() { - return spectrum; - } - - public void advance() { - available = true; - } - - @Override - public void connect(ConnectableInput other) { - other.connect(this); - } - - @Override - public void disconnect(ConnectableInput other) { - other.disconnect(this); - } - - public boolean isAvailable() { - return available; - } - -} diff --git a/src/com/jsyn/ports/UnitVariablePort.java b/src/com/jsyn/ports/UnitVariablePort.java deleted file mode 100644 index 60b64fd..0000000 --- a/src/com/jsyn/ports/UnitVariablePort.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * 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.softsynth.shared.time.ScheduledCommand; -import com.softsynth.shared.time.TimeStamp; - -public class UnitVariablePort extends UnitPort implements SettablePort { - private double value; - - public UnitVariablePort(String name, double defaultValue) { - super(name); - value = defaultValue; - } - - public UnitVariablePort(String name) { - super(name); - } - - public void setValue(double value) { - this.value = value; - } - - public void set(double value) { - this.value = value; - } - - public double get() { - return value; - } - - public double getValue() { - return value; - } - - @Override - public double getValue(int partNum) { - return value; - } - - @Override - public void set(int partNum, final double value, TimeStamp timeStamp) { - scheduleCommand(timeStamp, new ScheduledCommand() { - @Override - public void run() { - set(value); - } - }); - } -} diff --git a/src/com/jsyn/ports/package.html b/src/com/jsyn/ports/package.html deleted file mode 100644 index 3547618..0000000 --- a/src/com/jsyn/ports/package.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - -JSyn Ports - - -

Ports are used to pass audio data in and out of UnitGenerators. -They can also be used to connect UnitGenerators together so that signals can flow between them. -The UnitDataQueuePort contains a FIFO that will accept envelope and sample data. -

- - \ No newline at end of file diff --git a/src/com/jsyn/scope/AudioScope.java b/src/com/jsyn/scope/AudioScope.java deleted file mode 100644 index 7b2a98c..0000000 --- a/src/com/jsyn/scope/AudioScope.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright 2010 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.scope; - -import com.jsyn.Synthesizer; -import com.jsyn.ports.UnitOutputPort; -import com.jsyn.scope.swing.AudioScopeView; - -// TODO Auto and Manual triggers. -// TODO Auto scaling of vertical. -// TODO Fixed size Y scale knobs. -// TODO Pan back and forth around trigger. -// TODO Continuous capture -/** - * Digital oscilloscope for JSyn. - * - * @author Phil Burk (C) 2010 Mobileer Inc - */ -public class AudioScope { - public enum TriggerMode { - AUTO, NORMAL // , MANUAL - } - - public enum ViewMode { - WAVEFORM, SPECTRUM - } - - private AudioScopeView audioScopeView = null; - private AudioScopeModel audioScopeModel; - - public AudioScope(Synthesizer synth) { - audioScopeModel = new AudioScopeModel(synth); - } - - public AudioScopeProbe addProbe(UnitOutputPort output) { - return addProbe(output, 0); - } - - public AudioScopeProbe addProbe(UnitOutputPort output, int partIndex) { - return audioScopeModel.addProbe(output, partIndex); - } - - public void start() { - audioScopeModel.start(); - } - - public void stop() { - audioScopeModel.stop(); - } - - public AudioScopeModel getModel() { - return audioScopeModel; - } - - public AudioScopeView getView() { - if (audioScopeView == null) { - audioScopeView = new AudioScopeView(); - audioScopeView.setModel(audioScopeModel); - } - return audioScopeView; - } - - public void setTriggerMode(TriggerMode triggerMode) { - audioScopeModel.setTriggerMode(triggerMode); - } - - public void setTriggerSource(AudioScopeProbe probe) { - audioScopeModel.setTriggerSource(probe); - } - - public void setTriggerLevel(double level) { - getModel().getTriggerModel().getLevelModel().setDoubleValue(level); - } - - public double getTriggerLevel() { - return getModel().getTriggerModel().getLevelModel().getDoubleValue(); - } - - /** - * Not yet implemented. - * @param viewMode - */ - public void setViewMode(ViewMode viewMode) { - // TODO Auto-generated method stub - } - -} diff --git a/src/com/jsyn/scope/AudioScopeModel.java b/src/com/jsyn/scope/AudioScopeModel.java deleted file mode 100644 index 85c4413..0000000 --- a/src/com/jsyn/scope/AudioScopeModel.java +++ /dev/null @@ -1,157 +0,0 @@ -/* - * 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.scope; - -import java.util.ArrayList; -import java.util.concurrent.CopyOnWriteArrayList; - -import javax.swing.event.ChangeEvent; -import javax.swing.event.ChangeListener; - -import com.jsyn.Synthesizer; -import com.jsyn.ports.UnitOutputPort; -import com.jsyn.scope.AudioScope.TriggerMode; - -public class AudioScopeModel implements Runnable { - private static final int PRE_TRIGGER_SIZE = 32; - private Synthesizer synthesisEngine; - private ArrayList probes = new ArrayList(); - private CopyOnWriteArrayList changeListeners = new CopyOnWriteArrayList(); - private MultiChannelScopeProbeUnit probeUnit; - private double timeToArm; - private double period = 0.2; - private TriggerModel triggerModel; - - public AudioScopeModel(Synthesizer synth) { - this.synthesisEngine = synth; - triggerModel = new TriggerModel(); - } - - public AudioScopeProbe addProbe(UnitOutputPort output, int partIndex) { - AudioScopeProbe probe = new AudioScopeProbe(this, output, partIndex); - DefaultWaveTraceModel waveTraceModel = new DefaultWaveTraceModel(this, probes.size()); - probe.setWaveTraceModel(waveTraceModel); - probes.add(probe); - if (triggerModel.getSource() == null) { - triggerModel.setSource(probe); - } - return probe; - } - - public void start() { - stop(); - probeUnit = new MultiChannelScopeProbeUnit(probes.size(), triggerModel); - synthesisEngine.add(probeUnit); - for (int i = 0; i < probes.size(); i++) { - AudioScopeProbe probe = probes.get(i); - probe.getSource().connect(probe.getPartIndex(), probeUnit.input, i); - } - // Connect trigger signal to input of probe. - triggerModel.getSource().getSource() - .connect(triggerModel.getSource().getPartIndex(), probeUnit.trigger, 0); - probeUnit.start(); - - // Get synthesizer time in seconds. - timeToArm = synthesisEngine.getCurrentTime(); - probeUnit.arm(timeToArm, this); - } - - public void stop() { - if (probeUnit != null) { - for (int i = 0; i < probes.size(); i++) { - probeUnit.input.disconnectAll(i); - } - probeUnit.trigger.disconnectAll(); - probeUnit.stop(); - synthesisEngine.remove(probeUnit); - probeUnit = null; - } - } - - public AudioScopeProbe[] getProbes() { - return probes.toArray(new AudioScopeProbe[0]); - } - - public Synthesizer getSynthesizer() { - return synthesisEngine; - } - - @Override - public void run() { - fireChangeListeners(); - timeToArm = synthesisEngine.getCurrentTime(); - timeToArm += period; - probeUnit.arm(timeToArm, this); - } - - private void fireChangeListeners() { - ChangeEvent changeEvent = new ChangeEvent(this); - for (ChangeListener listener : changeListeners) { - listener.stateChanged(changeEvent); - } - // debug(); - } - - public void addChangeListener(ChangeListener changeListener) { - changeListeners.add(changeListener); - } - - public void removeChangeListener(ChangeListener changeListener) { - changeListeners.remove(changeListener); - } - - public void setTriggerMode(TriggerMode triggerMode) { - triggerModel.getModeModel().setSelectedItem(triggerMode); - } - - public void setTriggerSource(AudioScopeProbe probe) { - triggerModel.setSource(probe); - } - - public double getSample(int bufferIndex, int i) { - return probeUnit.getSample(bufferIndex, i); - } - - public int getFramesPerBuffer() { - return probeUnit.getFramesPerBuffer(); - } - - public int getFramesCaptured() { - return probeUnit.getFramesCaptured(); - } - - public int getVisibleSize() { - int size = 0; - if (probeUnit != null) { - size = probeUnit.getPostTriggerSize() + PRE_TRIGGER_SIZE; - if (size > getFramesCaptured()) { - size = getFramesCaptured(); - } - } - return size; - } - - public int getStartIndex() { - // TODO Add pan support here. - return getFramesCaptured() - getVisibleSize(); - } - - public TriggerModel getTriggerModel() { - return triggerModel; - } - -} diff --git a/src/com/jsyn/scope/AudioScopeProbe.java b/src/com/jsyn/scope/AudioScopeProbe.java deleted file mode 100644 index f1aad65..0000000 --- a/src/com/jsyn/scope/AudioScopeProbe.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright 2010 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.scope; - -import java.awt.Color; - -import javax.swing.JToggleButton.ToggleButtonModel; - -import com.jsyn.ports.UnitOutputPort; -import com.jsyn.swing.ExponentialRangeModel; - -/** - * Collect data from the source and make it available to the scope. - * - * @author Phil Burk (C) 2010 Mobileer Inc - */ -public class AudioScopeProbe { - // private UnitOutputPort output; - private WaveTraceModel waveTraceModel; - private AudioScopeModel audioScopeModel; - private UnitOutputPort source; - private int partIndex; - private Color color; - private ExponentialRangeModel verticalScaleModel; - private ToggleButtonModel autoScaleButtonModel; - private double MIN_RANGE = 0.01; - private double MAX_RANGE = 100.0; - - public AudioScopeProbe(AudioScopeModel audioScopeModel, UnitOutputPort source, int partIndex) { - this.audioScopeModel = audioScopeModel; - this.source = source; - this.partIndex = partIndex; - - verticalScaleModel = new ExponentialRangeModel("VScale", 1000, MIN_RANGE, MAX_RANGE, - MIN_RANGE); - autoScaleButtonModel = new ToggleButtonModel(); - autoScaleButtonModel.setSelected(true); - } - - public WaveTraceModel getWaveTraceModel() { - return waveTraceModel; - } - - public void setWaveTraceModel(WaveTraceModel waveTraceModel) { - this.waveTraceModel = waveTraceModel; - } - - public UnitOutputPort getSource() { - return source; - } - - public int getPartIndex() { - return partIndex; - } - - public Color getColor() { - return color; - } - - public void setColor(Color color) { - this.color = color; - } - - public void setAutoScaleEnabled(boolean enabled) { - autoScaleButtonModel.setSelected(enabled); - } - - public void setVerticalScale(double max) { - verticalScaleModel.setDoubleValue(max); - } - - public ExponentialRangeModel getVerticalScaleModel() { - return verticalScaleModel; - } - - public ToggleButtonModel getAutoScaleButtonModel() { - return autoScaleButtonModel; - } - -} diff --git a/src/com/jsyn/scope/DefaultWaveTraceModel.java b/src/com/jsyn/scope/DefaultWaveTraceModel.java deleted file mode 100644 index a123c0b..0000000 --- a/src/com/jsyn/scope/DefaultWaveTraceModel.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * 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.scope; - -public class DefaultWaveTraceModel implements WaveTraceModel { - private AudioScopeModel audioScopeModel; - private int bufferIndex; - - public DefaultWaveTraceModel(AudioScopeModel audioScopeModel, int bufferIndex) { - this.audioScopeModel = audioScopeModel; - this.bufferIndex = bufferIndex; - } - - @Override - public double getSample(int i) { - return audioScopeModel.getSample(bufferIndex, i); - } - - @Override - public int getSize() { - return audioScopeModel.getFramesCaptured(); - } - - @Override - public int getStartIndex() { - return audioScopeModel.getStartIndex(); - } - - @Override - public int getVisibleSize() { - return audioScopeModel.getVisibleSize(); - } - -} diff --git a/src/com/jsyn/scope/MultiChannelScopeProbeUnit.java b/src/com/jsyn/scope/MultiChannelScopeProbeUnit.java deleted file mode 100644 index 5be19f0..0000000 --- a/src/com/jsyn/scope/MultiChannelScopeProbeUnit.java +++ /dev/null @@ -1,246 +0,0 @@ -/* - * 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.scope; - -import com.jsyn.ports.UnitInputPort; -import com.jsyn.scope.AudioScope.TriggerMode; -import com.jsyn.unitgen.UnitGenerator; -import com.softsynth.shared.time.ScheduledCommand; - -/** - * Multi-channel scope probe with an independent trigger input. - * - * @author Phil Burk (C) 2009 Mobileer Inc - */ -public class MultiChannelScopeProbeUnit extends UnitGenerator { - // Signal that is captured. - public UnitInputPort input; - // Signal that triggers the probe. - public UnitInputPort trigger; - - // I am using ints instead of an enum for performance reasons. - private static final int STATE_IDLE = 0; - private static final int STATE_ARMED = 1; - private static final int STATE_READY = 2; - private static final int STATE_TRIGGERED = 3; - private int state = STATE_IDLE; - - private int numChannels; - private double[][] inputValues; - private static final int FRAMES_PER_BUFFER = 4096; // must be power of two - private static final int FRAMES_PER_BUFFER_MASK = FRAMES_PER_BUFFER - 1; - private Runnable callback; - - private TriggerModel triggerModel; - private int autoCountdown; - private int countdown; - private int postTriggerSize = 512; - SignalBuffer captureBuffer; - SignalBuffer displayBuffer; - - // Use double buffers. One for capture, one for display. - class SignalBuffer { - float[][] buffers; - private int writeCursor; - private int triggerIndex; - private int framesCaptured; - - SignalBuffer(int numChannels) { - buffers = new float[numChannels][]; - for (int j = 0; j < numChannels; j++) { - buffers[j] = new float[FRAMES_PER_BUFFER]; - } - } - - void reset() { - writeCursor = 0; - triggerIndex = 0; - framesCaptured = 0; - } - - public void saveChannelValue(int j, float value) { - buffers[j][writeCursor] = value; - } - - public void markTrigger() { - triggerIndex = writeCursor; - } - - public void bumpCursor() { - writeCursor = (writeCursor + 1) & FRAMES_PER_BUFFER_MASK; - if (writeCursor >= FRAMES_PER_BUFFER) { - writeCursor = 0; - } - if (framesCaptured < FRAMES_PER_BUFFER) { - framesCaptured += 1; - } - } - - private int convertInternalToExternalIndex(int internalIndex) { - if (framesCaptured < FRAMES_PER_BUFFER) { - return internalIndex; - } else { - return (internalIndex - writeCursor) & (FRAMES_PER_BUFFER_MASK); - } - } - - private int convertExternalToInternalIndex(int externalIndex) { - if (framesCaptured < FRAMES_PER_BUFFER) { - return externalIndex; - } else { - return (externalIndex + writeCursor) & (FRAMES_PER_BUFFER_MASK); - } - } - - public int getTriggerIndex() { - return convertInternalToExternalIndex(triggerIndex); - } - - public int getFramesCaptured() { - return framesCaptured; - } - - public float getSample(int bufferIndex, int sampleIndex) { - int index = convertExternalToInternalIndex(sampleIndex); - return buffers[bufferIndex][index]; - } - } - - public MultiChannelScopeProbeUnit(int numChannels, TriggerModel triggerModel) { - this.numChannels = numChannels; - captureBuffer = new SignalBuffer(numChannels); - displayBuffer = new SignalBuffer(numChannels); - this.triggerModel = triggerModel; - addPort(trigger = new UnitInputPort(numChannels, "Trigger")); - addPort(input = new UnitInputPort(numChannels, "Input")); - inputValues = new double[numChannels][]; - } - - private synchronized void switchBuffers() { - SignalBuffer temp = captureBuffer; - captureBuffer = displayBuffer; - displayBuffer = temp; - } - - private void internalArm(Runnable callback) { - this.callback = callback; - state = STATE_ARMED; - captureBuffer.reset(); - } - - class ScheduledArm implements ScheduledCommand { - private Runnable callback; - - ScheduledArm(Runnable callback) { - this.callback = callback; - } - - @Override - public void run() { - internalArm(this.callback); - } - } - - /** Arm the probe at a future time. */ - public void arm(double time, Runnable callback) { - ScheduledArm command = new ScheduledArm(callback); - getSynthesisEngine().scheduleCommand(time, command); - } - - @Override - public void generate(int start, int limit) { - if (state != STATE_IDLE) { - TriggerMode triggerMode = triggerModel.getMode(); - double triggerLevel = triggerModel.getTriggerLevel(); - double[] triggerValues = trigger.getValues(); - - for (int j = 0; j < numChannels; j++) { - inputValues[j] = input.getValues(j); - } - - for (int i = start; i < limit; i++) { - // Capture one sample from each channel. - for (int j = 0; j < numChannels; j++) { - captureBuffer.saveChannelValue(j, (float) inputValues[j][i]); - } - captureBuffer.bumpCursor(); - - switch (state) { - case STATE_ARMED: - if (triggerValues[i] <= triggerLevel) { - state = STATE_READY; - autoCountdown = 44100; - } - break; - - case STATE_READY: { - boolean triggered = false; - if (triggerValues[i] > triggerLevel) { - triggered = true; - } else if (triggerMode.equals(TriggerMode.AUTO)) { - if (--autoCountdown == 0) { - triggered = true; - } - } - if (triggered) { - captureBuffer.markTrigger(); - state = STATE_TRIGGERED; - countdown = postTriggerSize; - } - } - break; - - case STATE_TRIGGERED: - countdown -= 1; - if (countdown <= 0) { - state = STATE_IDLE; - switchBuffers(); - fireCallback(); - } - break; - } - } - } - } - - private void fireCallback() { - if (callback != null) { - callback.run(); - } - } - - public float getSample(int bufferIndex, int sampleIndex) { - return displayBuffer.getSample(bufferIndex, sampleIndex); - } - - public int getTriggerIndex() { - return displayBuffer.getTriggerIndex(); - } - - public int getFramesCaptured() { - return displayBuffer.getFramesCaptured(); - } - - public int getFramesPerBuffer() { - return FRAMES_PER_BUFFER; - } - - public int getPostTriggerSize() { - return postTriggerSize; - } - -} diff --git a/src/com/jsyn/scope/TriggerModel.java b/src/com/jsyn/scope/TriggerModel.java deleted file mode 100644 index 0367d71..0000000 --- a/src/com/jsyn/scope/TriggerModel.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * 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.scope; - -import javax.swing.DefaultComboBoxModel; - -import com.jsyn.scope.AudioScope.TriggerMode; -import com.jsyn.swing.ExponentialRangeModel; - -public class TriggerModel { - private ExponentialRangeModel levelModel; - private DefaultComboBoxModel modeModel; - private AudioScopeProbe source; - - public TriggerModel() { - modeModel = new DefaultComboBoxModel(); - modeModel.addElement(TriggerMode.AUTO); - modeModel.addElement(TriggerMode.NORMAL); - levelModel = new ExponentialRangeModel("TriggerLevel", 1000, 0.01, 2.0, 0.04); - } - - public AudioScopeProbe getSource() { - return source; - } - - public void setSource(AudioScopeProbe source) { - this.source = source; - } - - public ExponentialRangeModel getLevelModel() { - return levelModel; - } - - public void setLevelModel(ExponentialRangeModel levelModel) { - this.levelModel = levelModel; - } - - public DefaultComboBoxModel getModeModel() { - return modeModel; - } - - public void setModeModel(DefaultComboBoxModel modeModel) { - this.modeModel = modeModel; - } - - public double getTriggerLevel() { - return levelModel.getDoubleValue(); - } - - public TriggerMode getMode() { - return (TriggerMode) modeModel.getSelectedItem(); - } -} diff --git a/src/com/jsyn/scope/WaveTraceModel.java b/src/com/jsyn/scope/WaveTraceModel.java deleted file mode 100644 index e9d8bf9..0000000 --- a/src/com/jsyn/scope/WaveTraceModel.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * 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.scope; - -public interface WaveTraceModel { - int getSize(); - - int getVisibleSize(); - - int getStartIndex(); - - double getSample(int i); -} diff --git a/src/com/jsyn/scope/swing/AudioScopeProbeView.java b/src/com/jsyn/scope/swing/AudioScopeProbeView.java deleted file mode 100644 index 59526e1..0000000 --- a/src/com/jsyn/scope/swing/AudioScopeProbeView.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2010 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.scope.swing; - -import com.jsyn.scope.AudioScopeProbe; - -/** - * Wave display associated with a probe. - * - * @author Phil Burk (C) 2010 Mobileer Inc - */ -public class AudioScopeProbeView { - private AudioScopeProbe probeModel; - private WaveTraceView waveTrace; - - public AudioScopeProbeView(AudioScopeProbe probeModel) { - this.probeModel = probeModel; - waveTrace = new WaveTraceView(probeModel.getAutoScaleButtonModel(), - probeModel.getVerticalScaleModel()); - waveTrace.setModel(probeModel.getWaveTraceModel()); - } - - public WaveTraceView getWaveTraceView() { - return waveTrace; - } - - public AudioScopeProbe getModel() { - return probeModel; - } - -} diff --git a/src/com/jsyn/scope/swing/AudioScopeView.java b/src/com/jsyn/scope/swing/AudioScopeView.java deleted file mode 100644 index ec1afa3..0000000 --- a/src/com/jsyn/scope/swing/AudioScopeView.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * 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.scope.swing; - -import java.awt.BorderLayout; -import java.awt.Color; -import java.awt.Dimension; -import java.util.ArrayList; - -import javax.swing.JPanel; -import javax.swing.event.ChangeEvent; -import javax.swing.event.ChangeListener; - -import com.jsyn.scope.AudioScopeModel; -import com.jsyn.scope.AudioScopeProbe; - -public class AudioScopeView extends JPanel { - private static final long serialVersionUID = -7507986850757860853L; - private AudioScopeModel audioScopeModel; - private ArrayList probeViews = new ArrayList(); - private MultipleWaveDisplay multipleWaveDisplay; - private boolean showControls = false; - private ScopeControlPanel controlPanel = null; - - public AudioScopeView() { - setBackground(Color.GREEN); - } - - public void setModel(AudioScopeModel audioScopeModel) { - this.audioScopeModel = audioScopeModel; - // Create a view for each probe. - probeViews.clear(); - for (AudioScopeProbe probeModel : audioScopeModel.getProbes()) { - AudioScopeProbeView audioScopeProbeView = new AudioScopeProbeView(probeModel); - probeViews.add(audioScopeProbeView); - } - setupGUI(); - - // Listener for signal change events. - audioScopeModel.addChangeListener(new ChangeListener() { - @Override - public void stateChanged(ChangeEvent e) { - multipleWaveDisplay.repaint(); - } - }); - - } - - private void setupGUI() { - removeAll(); - setLayout(new BorderLayout()); - multipleWaveDisplay = new MultipleWaveDisplay(); - - for (AudioScopeProbeView probeView : probeViews) { - multipleWaveDisplay.addWaveTrace(probeView.getWaveTraceView()); - probeView.getModel().setColor(probeView.getWaveTraceView().getColor()); - } - - add(multipleWaveDisplay, BorderLayout.CENTER); - - setMinimumSize(new Dimension(400, 200)); - setPreferredSize(new Dimension(600, 250)); - setMaximumSize(new Dimension(1200, 300)); - } - - /** @deprecated Use setControlsVisible() instead. */ - @Deprecated - public void setShowControls(boolean show) { - setControlsVisible(show); - } - - public void setControlsVisible(boolean show) { - if (this.showControls) { - if (!show && (controlPanel != null)) { - remove(controlPanel); - } - } else { - if (show) { - if (controlPanel == null) { - controlPanel = new ScopeControlPanel(this); - } - add(controlPanel, BorderLayout.EAST); - validate(); - } - } - - this.showControls = show; - } - - public AudioScopeModel getModel() { - return audioScopeModel; - } - - public AudioScopeProbeView[] getProbeViews() { - return probeViews.toArray(new AudioScopeProbeView[0]); - } - -} diff --git a/src/com/jsyn/scope/swing/MultipleWaveDisplay.java b/src/com/jsyn/scope/swing/MultipleWaveDisplay.java deleted file mode 100644 index 0259850..0000000 --- a/src/com/jsyn/scope/swing/MultipleWaveDisplay.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2011 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.scope.swing; - -import java.awt.Color; -import java.awt.Graphics; -import java.util.ArrayList; - -import javax.swing.JPanel; - -/** - * Display multiple waveforms together in different colors. - * - * @author Phil Burk (C) 2011 Mobileer Inc - */ -public class MultipleWaveDisplay extends JPanel { - private static final long serialVersionUID = -5157397030540800373L; - - private ArrayList waveTraceViews = new ArrayList(); - private Color[] defaultColors = { - Color.BLUE, Color.RED, Color.BLACK, Color.MAGENTA, Color.GREEN, Color.ORANGE - }; - - public MultipleWaveDisplay() { - setBackground(Color.WHITE); - } - - public void addWaveTrace(WaveTraceView waveTraceView) { - if (waveTraceView.getColor() == null) { - waveTraceView.setColor(defaultColors[waveTraceViews.size() % defaultColors.length]); - } - waveTraceViews.add(waveTraceView); - } - - @Override - public void paintComponent(Graphics g) { - super.paintComponent(g); - int width = getWidth(); - int height = getHeight(); - for (WaveTraceView waveTraceView : waveTraceViews.toArray(new WaveTraceView[0])) { - waveTraceView.drawWave(g, width, height); - } - } -} diff --git a/src/com/jsyn/scope/swing/ScopeControlPanel.java b/src/com/jsyn/scope/swing/ScopeControlPanel.java deleted file mode 100644 index 7f3a026..0000000 --- a/src/com/jsyn/scope/swing/ScopeControlPanel.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * 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.scope.swing; - -import java.awt.GridLayout; - -import javax.swing.JPanel; - -import com.jsyn.scope.AudioScopeModel; - -public class ScopeControlPanel extends JPanel { - private static final long serialVersionUID = 7738305116057614812L; - private AudioScopeModel audioScopeModel; - private ScopeTriggerPanel triggerPanel; - private JPanel probeRows; - - public ScopeControlPanel(AudioScopeView audioScopeView) { - setLayout(new GridLayout(0, 1)); - this.audioScopeModel = audioScopeView.getModel(); - triggerPanel = new ScopeTriggerPanel(audioScopeModel); - add(triggerPanel); - - probeRows = new JPanel(); - probeRows.setLayout(new GridLayout(1, 0)); - add(probeRows); - for (AudioScopeProbeView probeView : audioScopeView.getProbeViews()) { - ScopeProbePanel probePanel = new ScopeProbePanel(probeView); - probeRows.add(probePanel); - } - } - -} diff --git a/src/com/jsyn/scope/swing/ScopeProbePanel.java b/src/com/jsyn/scope/swing/ScopeProbePanel.java deleted file mode 100644 index 9cb82af..0000000 --- a/src/com/jsyn/scope/swing/ScopeProbePanel.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * 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.scope.swing; - -import java.awt.BorderLayout; -import java.awt.Color; -import java.awt.Dimension; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; - -import javax.swing.BorderFactory; -import javax.swing.JCheckBox; -import javax.swing.JPanel; -import javax.swing.JToggleButton.ToggleButtonModel; - -import com.jsyn.scope.AudioScopeProbe; -import com.jsyn.swing.RotaryTextController; - -public class ScopeProbePanel extends JPanel { - private static final long serialVersionUID = 4511589171299298548L; - private AudioScopeProbeView audioScopeProbeView; - private AudioScopeProbe audioScopeProbe; - private RotaryTextController verticalScaleKnob; - private JCheckBox autoBox; - private ToggleButtonModel autoScaleModel; - - public ScopeProbePanel(AudioScopeProbeView probeView) { - this.audioScopeProbeView = probeView; - setLayout(new BorderLayout()); - - setBorder(BorderFactory.createLineBorder(Color.GRAY, 3)); - - // Add a colored box to match the waveform color. - JPanel colorPanel = new JPanel(); - colorPanel.setMinimumSize(new Dimension(40, 40)); - audioScopeProbe = probeView.getModel(); - colorPanel.setBackground(audioScopeProbe.getColor()); - add(colorPanel, BorderLayout.NORTH); - - // Knob for tweaking vertical range. - verticalScaleKnob = new RotaryTextController(audioScopeProbeView.getWaveTraceView() - .getVerticalRangeModel(), 5); - add(verticalScaleKnob, BorderLayout.CENTER); - verticalScaleKnob.setTitle("YScale"); - - // Auto ranging checkbox. - autoBox = new JCheckBox("Auto"); - autoScaleModel = audioScopeProbeView.getWaveTraceView().getAutoButtonModel(); - autoScaleModel.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - ToggleButtonModel model = (ToggleButtonModel) e.getSource(); - boolean enabled = !model.isSelected(); - System.out.println("Knob enabled = " + enabled); - verticalScaleKnob.setEnabled(!model.isSelected()); - } - }); - autoBox.setModel(autoScaleModel); - add(autoBox, BorderLayout.SOUTH); - - verticalScaleKnob.setEnabled(!autoScaleModel.isSelected()); - - setMinimumSize(new Dimension(80, 100)); - setPreferredSize(new Dimension(80, 150)); - setMaximumSize(new Dimension(120, 200)); - } - -} diff --git a/src/com/jsyn/scope/swing/ScopeTriggerPanel.java b/src/com/jsyn/scope/swing/ScopeTriggerPanel.java deleted file mode 100644 index 9c22aa1..0000000 --- a/src/com/jsyn/scope/swing/ScopeTriggerPanel.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * 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.scope.swing; - -import java.awt.BorderLayout; - -import javax.swing.DefaultComboBoxModel; -import javax.swing.JComboBox; -import javax.swing.JPanel; - -import com.jsyn.scope.AudioScopeModel; -import com.jsyn.scope.TriggerModel; -import com.jsyn.scope.AudioScope.TriggerMode; -import com.jsyn.swing.RotaryTextController; - -public class ScopeTriggerPanel extends JPanel { - private static final long serialVersionUID = 4511589171299298548L; - private JComboBox> triggerModeComboBox; - private RotaryTextController triggerLevelKnob; - - public ScopeTriggerPanel(AudioScopeModel audioScopeModel) { - setLayout(new BorderLayout()); - TriggerModel triggerModel = audioScopeModel.getTriggerModel(); - triggerModeComboBox = new JComboBox(triggerModel.getModeModel()); - add(triggerModeComboBox, BorderLayout.NORTH); - - triggerLevelKnob = new RotaryTextController(triggerModel.getLevelModel(), 5); - - add(triggerLevelKnob, BorderLayout.CENTER); - triggerLevelKnob.setTitle("Trigger Level"); - } - -} diff --git a/src/com/jsyn/scope/swing/WaveTraceView.java b/src/com/jsyn/scope/swing/WaveTraceView.java deleted file mode 100644 index 849a6f4..0000000 --- a/src/com/jsyn/scope/swing/WaveTraceView.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * 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.scope.swing; - -import java.awt.Color; -import java.awt.Graphics; - -import javax.swing.JToggleButton.ToggleButtonModel; - -import com.jsyn.scope.WaveTraceModel; -import com.jsyn.swing.ExponentialRangeModel; - -public class WaveTraceView { - private static final double AUTO_DECAY = 0.95; - private WaveTraceModel waveTraceModel; - private Color color; - private ExponentialRangeModel verticalScaleModel; - private ToggleButtonModel autoScaleButtonModel; - - private double xScaler; - private double yScalar; - private int centerY; - - public WaveTraceView(ToggleButtonModel autoButtonModel, ExponentialRangeModel verticalRangeModel) { - this.verticalScaleModel = verticalRangeModel; - this.autoScaleButtonModel = autoButtonModel; - } - - public Color getColor() { - return color; - } - - public void setColor(Color color) { - this.color = color; - } - - public ExponentialRangeModel getVerticalRangeModel() { - return verticalScaleModel; - } - - public ToggleButtonModel getAutoButtonModel() { - return autoScaleButtonModel; - } - - public void setModel(WaveTraceModel waveTraceModel) { - this.waveTraceModel = waveTraceModel; - } - - public int convertRealToY(double r) { - return centerY - (int) (yScalar * r); - } - - public void drawWave(Graphics g, int width, int height) { - double sampleMax = 0.0; - double sampleMin = 0.0; - g.setColor(color); - int numSamples = waveTraceModel.getVisibleSize(); - if (numSamples > 0) { - xScaler = (double) width / numSamples; - // Scale by 0.5 because it is bipolar. - yScalar = 0.5 * height / verticalScaleModel.getDoubleValue(); - centerY = height / 2; - - // Calculate position of first point. - int x1 = 0; - int offset = waveTraceModel.getStartIndex(); - double value = waveTraceModel.getSample(offset); - int y1 = convertRealToY(value); - - // Draw lines to remaining points. - for (int i = 1; i < numSamples; i++) { - int x2 = (int) (i * xScaler); - value = waveTraceModel.getSample(offset + i); - int y2 = convertRealToY(value); - g.drawLine(x1, y1, x2, y2); - x1 = x2; - y1 = y2; - // measure min and max for auto - if (value > sampleMax) { - sampleMax = value; - } else if (value < sampleMin) { - sampleMin = value; - } - } - - autoScaleRange(sampleMax); - } - } - - // Autoscale the vertical range. - private void autoScaleRange(double sampleMax) { - if (autoScaleButtonModel.isSelected()) { - double scaledMax = sampleMax * 1.1; - double current = verticalScaleModel.getDoubleValue(); - if (scaledMax > current) { - verticalScaleModel.setDoubleValue(scaledMax); - } else { - double decayed = current * AUTO_DECAY; - if (decayed > verticalScaleModel.getMinimum()) { - if (scaledMax < decayed) { - verticalScaleModel.setDoubleValue(decayed); - } - } - } - } - } - -} diff --git a/src/com/jsyn/swing/ASCIIMusicKeyboard.java b/src/com/jsyn/swing/ASCIIMusicKeyboard.java deleted file mode 100644 index f1379d8..0000000 --- a/src/com/jsyn/swing/ASCIIMusicKeyboard.java +++ /dev/null @@ -1,193 +0,0 @@ -/* - * Copyright 2012 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.swing; - -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.awt.event.KeyEvent; -import java.awt.event.KeyListener; -import java.util.HashSet; - -import javax.swing.JButton; -import javax.swing.JCheckBox; -import javax.swing.JLabel; -import javax.swing.JPanel; - -/** - * Support for playing musical scales on the ASCII keyboard of a computer. Has a Sustain checkbox - * that simulates a sustain pedal. Auto-repeat keys are detected and suppressed. - * - * @author Phil Burk (C) 2012 Mobileer Inc - */ -@SuppressWarnings("serial") -public abstract class ASCIIMusicKeyboard extends JPanel { - private final JCheckBox sustainBox; - private final JButton focusButton; - public static final String PENTATONIC_KEYS = "zxcvbasdfgqwert12345"; - public static final String SEPTATONIC_KEYS = "zxcvbnmasdfghjqwertyu1234567890"; - private String keyboardLayout = SEPTATONIC_KEYS; /* default music keyboard layout */ - private int basePitch = 48; - private final KeyListener keyListener; - private final JLabel countLabel; - private int onCount; - private int offCount; - private int pressedCount; - private int releasedCount; - private final HashSet pressedKeys = new HashSet(); - private final HashSet onKeys = new HashSet(); - - public ASCIIMusicKeyboard() { - focusButton = new JButton("Click here to play ASCII keys."); - focusButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent arg0) { - } - }); - keyListener = new KeyListener() { - - @Override - public void keyPressed(KeyEvent e) { - int key = e.getKeyChar(); - int idx = keyboardLayout.indexOf(key); - System.out.println("keyPressed " + idx); - if (idx >= 0) { - if (!pressedKeys.contains(idx)) { - keyOn(convertIndexToPitch(idx)); - onCount++; - pressedKeys.add(idx); - onKeys.add(idx); - } - } - pressedCount++; - updateCountLabel(); - } - - @Override - public void keyReleased(KeyEvent e) { - int key = e.getKeyChar(); - int idx = keyboardLayout.indexOf(key); - System.out.println("keyReleased " + idx); - if (idx >= 0) { - if (!sustainBox.isSelected()) { - noteOffInternal(idx); - onKeys.remove(idx); - } - pressedKeys.remove(idx); - } - releasedCount++; - updateCountLabel(); - } - - @Override - public void keyTyped(KeyEvent arg0) { - } - }; - focusButton.addKeyListener(keyListener); - add(focusButton); - - sustainBox = new JCheckBox("sustain"); - sustainBox.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent arg0) { - if (!sustainBox.isSelected()) { - for (Integer noteIndex : onKeys) { - noteOffInternal(noteIndex); - } - onKeys.clear(); - } - } - }); - add(sustainBox); - sustainBox.addKeyListener(keyListener); - - countLabel = new JLabel("0"); - add(countLabel); - } - - private void noteOffInternal(int idx) { - keyOff(convertIndexToPitch(idx)); - offCount++; - } - - protected void updateCountLabel() { - countLabel.setText(onCount + "/" + offCount + ", " + pressedCount + "/" + releasedCount); - } - - /** - * Convert index to a MIDI noteNumber in a major scale. Result will be offset by the basePitch. - */ - public int convertIndexToPitch(int keyIndex) { - int scale[] = { - 0, 2, 4, 5, 7, 9, 11 - }; - int octave = keyIndex / scale.length; - int idx = keyIndex % scale.length; - int pitch = (octave * 12) + scale[idx]; - return pitch + basePitch; - } - - /** - * This will be called when a key is released. It may also be called for sustaining notes when - * the Sustain check box is turned off. - * - * @param keyIndex - */ - public abstract void keyOff(int keyIndex); - - /** - * This will be called when a key is pressed. - * - * @param keyIndex - */ - public abstract void keyOn(int keyIndex); - - public String getKeyboardLayout() { - return keyboardLayout; - } - - /** - * Specify the keys that will be active for music. - * For example "qwertyui". - * If the first character in the layout is - * pressed then keyOn() will be called with 0. Default is SEPTATONIC_KEYS. - * - * @param keyboardLayout defines order of playable keys - */ - public void setKeyboardLayout(String keyboardLayout) { - this.keyboardLayout = keyboardLayout; - } - - public int getBasePitch() { - return basePitch; - } - - /** - * Define offset used by convertIndexToPitch(). - * - * @param basePitch - */ - public void setBasePitch(int basePitch) { - this.basePitch = basePitch; - } - - /** - * @return - */ - public KeyListener getKeyListener() { - return keyListener; - } -} diff --git a/src/com/jsyn/swing/DoubleBoundedRangeModel.java b/src/com/jsyn/swing/DoubleBoundedRangeModel.java deleted file mode 100644 index 647e8da..0000000 --- a/src/com/jsyn/swing/DoubleBoundedRangeModel.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright 2002 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.swing; - -import javax.swing.DefaultBoundedRangeModel; - -/** - * Double precision data model for sliders and knobs. Maps integer range info to a double value. - * - * @author Phil Burk, (C) 2002 SoftSynth.com, PROPRIETARY and CONFIDENTIAL - */ -public class DoubleBoundedRangeModel extends DefaultBoundedRangeModel { - private static final long serialVersionUID = 284361767102120148L; - protected String name; - private double dmin; - private double dmax; - - public DoubleBoundedRangeModel(String name, int resolution, double dmin, double dmax, - double dval) { - this.name = name; - this.dmin = dmin; - this.dmax = dmax; - setMinimum(0); - setMaximum(resolution); - setDoubleValue(dval); - } - - public boolean equivalentTo(Object other) { - if (!(other instanceof DoubleBoundedRangeModel)) - return false; - DoubleBoundedRangeModel otherModel = (DoubleBoundedRangeModel) other; - return (getValue() == otherModel.getValue()); - } - - /** Set name of value. This may be used in labels or when saving the value. */ - public void setName(String name) { - this.name = name; - } - - public String getName() { - return name; - } - - public double getDoubleMinimum() { - return dmin; - } - - public double getDoubleMaximum() { - return dmax; - } - - public double sliderToDouble(int sliderValue) { - double doubleMin = getDoubleMinimum(); - return doubleMin + ((getDoubleMaximum() - doubleMin) * sliderValue / getMaximum()); - } - - public int doubleToSlider(double dval) { - double doubleMin = getDoubleMinimum(); - // TODO consider using Math.floor() instead of (int) if not too slow. - return (int) Math.round(getMaximum() * (dval - doubleMin) - / (getDoubleMaximum() - doubleMin)); - } - - public double getDoubleValue() { - return sliderToDouble(getValue()); - } - - public void setDoubleValue(double dval) { - setValue(doubleToSlider(dval)); - } - -} diff --git a/src/com/jsyn/swing/DoubleBoundedRangeSlider.java b/src/com/jsyn/swing/DoubleBoundedRangeSlider.java deleted file mode 100644 index 3642221..0000000 --- a/src/com/jsyn/swing/DoubleBoundedRangeSlider.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright 2002 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.swing; - -import java.util.Hashtable; - -import javax.swing.BorderFactory; -import javax.swing.JLabel; -import javax.swing.JSlider; -import javax.swing.border.TitledBorder; -import javax.swing.event.ChangeEvent; -import javax.swing.event.ChangeListener; - -import com.jsyn.util.NumericOutput; - -/** - * Slider that takes a DoubleBoundedRangeModel. It displays the current value in a titled border. - * - * @author Phil Burk, (C) 2002 SoftSynth.com, PROPRIETARY and CONFIDENTIAL - */ - -public class DoubleBoundedRangeSlider extends JSlider { - /** - * - */ - private static final long serialVersionUID = -440390322602838998L; - /** Places after decimal point for display. */ - private int places; - - public DoubleBoundedRangeSlider(DoubleBoundedRangeModel model) { - this(model, 5); - } - - public DoubleBoundedRangeSlider(DoubleBoundedRangeModel model, int places) { - super(model); - this.places = places; - setBorder(BorderFactory.createTitledBorder(generateTitleText())); - model.addChangeListener(new ChangeListener() { - @Override - public void stateChanged(ChangeEvent e) { - updateTitle(); - } - }); - } - - protected void updateTitle() { - TitledBorder border = (TitledBorder) getBorder(); - if (border != null) { - border.setTitle(generateTitleText()); - repaint(); - } - } - - String generateTitleText() { - DoubleBoundedRangeModel model = (DoubleBoundedRangeModel) getModel(); - double val = model.getDoubleValue(); - String valText = NumericOutput.doubleToString(val, 0, places); - return model.getName() + " = " + valText; - } - - public void makeStandardLabels(int labelSpacing) { - setMajorTickSpacing(labelSpacing / 2); - setLabelTable(createStandardLabels(labelSpacing)); - setPaintTicks(true); - setPaintLabels(true); - } - - public double nextLabelValue(double current, double delta) { - return current + delta; - } - - public void makeLabels(double start, double delta, int places) { - DoubleBoundedRangeModel model = (DoubleBoundedRangeModel) getModel(); - // Create the label table - Hashtable labelTable = new Hashtable(); - double dval = start; - while (dval <= model.getDoubleMaximum()) { - int sliderValue = model.doubleToSlider(dval); - String text = NumericOutput.doubleToString(dval, 0, places); - labelTable.put(new Integer(sliderValue), new JLabel(text)); - dval = nextLabelValue(dval, delta); - } - setLabelTable(labelTable); - setPaintLabels(true); - } - -} diff --git a/src/com/jsyn/swing/DoubleBoundedTextField.java b/src/com/jsyn/swing/DoubleBoundedTextField.java deleted file mode 100644 index 3301bb1..0000000 --- a/src/com/jsyn/swing/DoubleBoundedTextField.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright 2000 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.swing; - -import java.awt.Color; -import java.awt.event.KeyAdapter; -import java.awt.event.KeyEvent; - -import javax.swing.JTextField; -import javax.swing.SwingConstants; -import javax.swing.event.ChangeEvent; -import javax.swing.event.ChangeListener; - -/** - * TextField that turns pink when modified, and white when the value is entered. - * - * @author (C) 2000-2010 Phil Burk, Mobileer Inc - * @version 16 - */ - -public class DoubleBoundedTextField extends JTextField { - private static final long serialVersionUID = 6882779668177620812L; - boolean modified = false; - int numCharacters; - private DoubleBoundedRangeModel model; - - public DoubleBoundedTextField(DoubleBoundedRangeModel pModel, int numCharacters) { - super(numCharacters); - this.model = pModel; - this.numCharacters = numCharacters; - setHorizontalAlignment(SwingConstants.LEADING); - setValue(model.getDoubleValue()); - addKeyListener(new KeyAdapter() { - @Override - public void keyTyped(KeyEvent e) { - if (e.getKeyChar() == '\n') { - model.setDoubleValue(getValue()); - } else { - markDirty(); - } - } - }); - model.addChangeListener(new ChangeListener() { - @Override - public void stateChanged(ChangeEvent e) { - setValue(model.getDoubleValue()); - } - }); - } - - private void markDirty() { - modified = true; - setBackground(Color.pink); - repaint(); - } - - private void markClean() { - modified = false; - setBackground(Color.white); - setCaretPosition(0); - repaint(); - } - - @Override - public void setText(String text) { - markDirty(); - super.setText(text); - } - - private double getValue() throws NumberFormatException { - double val = Double.valueOf(getText()).doubleValue(); - markClean(); - return val; - } - - private void setValue(double value) { - super.setText(String.format("%6.4f", value)); - markClean(); - } -} diff --git a/src/com/jsyn/swing/EnvelopeEditorBox.java b/src/com/jsyn/swing/EnvelopeEditorBox.java deleted file mode 100644 index aab5762..0000000 --- a/src/com/jsyn/swing/EnvelopeEditorBox.java +++ /dev/null @@ -1,573 +0,0 @@ -/* - * Copyright 1997 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.swing; - -import java.awt.Color; -import java.awt.Graphics; -import java.awt.event.MouseEvent; -import java.awt.event.MouseListener; -import java.awt.event.MouseMotionListener; -import java.util.ArrayList; - -import com.jsyn.data.SegmentedEnvelope; -import com.jsyn.unitgen.VariableRateDataReader; - -/** - * Edit a list of ordered duration,value pairs suitable for use with a SegmentedEnvelope. - * - * @author (C) 1997-2013 Phil Burk, SoftSynth.com - * @see EnvelopePoints - * @see SegmentedEnvelope - * @see VariableRateDataReader - */ - -/* ========================================================================== */ -public class EnvelopeEditorBox extends XYController implements MouseListener, MouseMotionListener { - EnvelopePoints points; - ArrayList listeners = new ArrayList(); - int dragIndex = -1; - double dragLowLimit; - double dragHighLimit; - double draggedPoint[]; - double xBefore; // WX value before point - double xPicked; // WX value of picked point - double dragWX; - double dragWY; - int maxPoints = Integer.MAX_VALUE; - int radius = 4; - double verticalBarSpacing = 1.0; - boolean verticalBarsEnabled = false; - double maximumXRange = Double.MAX_VALUE; - double minimumXRange = 0.1; - int rangeStart = -1; // gx coordinates - int rangeEnd = -1; - int mode = EDIT_POINTS; - public final static int EDIT_POINTS = 0; - public final static int SELECT_SUSTAIN = 1; - public final static int SELECT_RELEASE = 2; - - Color rangeColor = Color.RED; - Color sustainColor = Color.BLUE; - Color releaseColor = Color.YELLOW; - Color overlapColor = Color.GREEN; - Color firstLineColor = Color.GRAY; - - public interface EditListener { - public void objectEdited(Object editor, Object edited); - } - - public EnvelopeEditorBox() { - addMouseListener(this); - addMouseMotionListener(this); - } - - public void setMaximumXRange(double maxXRange) { - maximumXRange = maxXRange; - } - - public double getMaximumXRange() { - return maximumXRange; - } - - public void setMinimumXRange(double minXRange) { - minimumXRange = minXRange; - } - - public double getMinimumXRange() { - return minimumXRange; - } - - public void setSelection(int start, int end) { - switch (mode) { - case SELECT_SUSTAIN: - points.setSustainLoop(start, end); - break; - case SELECT_RELEASE: - points.setReleaseLoop(start, end); - break; - } - // System.out.println("start = " + start + ", end = " + end ); - } - - /** Set mode to either EDIT_POINTS or SELECT_SUSTAIN, SELECT_RELEASE; */ - public void setMode(int mode) { - this.mode = mode; - } - - public int getMode() { - return mode; - } - - /** - * Add a listener to receive edit events. Listener will be passed the editor object and the - * edited object. - */ - public void addEditListener(EditListener listener) { - listeners.add(listener); - } - - public void removeEditListener(EditListener listener) { - listeners.remove(listener); - } - - /** Send event to every subscribed listener. */ - public void fireObjectEdited() { - for (EditListener listener : listeners) { - listener.objectEdited(this, points); - } - } - - public void setMaxPoints(int maxPoints) { - this.maxPoints = maxPoints; - } - - public int getMaxPoints() { - return maxPoints; - } - - public int getNumPoints() { - return points.size(); - } - - public void setPoints(EnvelopePoints points) { - this.points = points; - setMaxWorldY(points.getMaximumValue()); - } - - public EnvelopePoints getPoints() { - return points; - } - - /** - * Return index of point before this X position. - */ - private int findPointBefore(double wx) { - int pnt = -1; - double px = 0.0; - xBefore = 0.0; - for (int i = 0; i < points.size(); i++) { - px += points.getDuration(i); - if (px > wx) - break; - pnt = i; - xBefore = px; - } - return pnt; - } - - private int pickPoint(double wx, double wxAperture, double wy, double wyAperture) { - double px = 0.0; - double wxLow = wx - wxAperture; - double wxHigh = wx + wxAperture; - // System.out.println("wxLow = " + wxLow + ", wxHigh = " + wxHigh ); - double wyLow = wy - wyAperture; - double wyHigh = wy + wyAperture; - // System.out.println("wyLow = " + wyLow + ", wyHigh = " + wyHigh ); - double wxScale = 1.0 / wxAperture; // only divide once, then multiply - double wyScale = 1.0 / wyAperture; - int bestPoint = -1; - double bestDistance = Double.MAX_VALUE; - for (int i = 0; i < points.size(); i++) { - double dar[] = points.getPoint(i); - px += dar[0]; - double py = dar[1]; - // System.out.println("px = " + px + ", py = " + py ); - if ((px > wxLow) && (px < wxHigh) && (py > wyLow) && (py < wyHigh)) { - /* Inside pick range. Calculate distance squared. */ - double ndx = (px - wx) * wxScale; - double ndy = (py - wy) * wyScale; - double dist = (ndx * ndx) + (ndy * ndy); - // System.out.println("dist = " + dist ); - if (dist < bestDistance) { - bestPoint = i; - bestDistance = dist; - xPicked = px; - } - } - } - return bestPoint; - } - - private void clickDownRange(boolean shiftDown, int gx, int gy) { - setSelection(-1, -1); - rangeStart = rangeEnd = gx; - repaint(); - } - - private void dragRange(int gx, int gy) { - rangeEnd = gx; - repaint(); - } - - private void clickUpRange(int gx, int gy) { - dragRange(gx, gy); - if (rangeEnd < rangeStart) { - int temp = rangeEnd; - rangeEnd = rangeStart; - rangeStart = temp; - } - // System.out.println("clickUpRange: gx = " + gx + ", rangeStart = " + - // rangeStart ); - double wx = convertGXtoWX(rangeStart); - int i0 = findPointBefore(wx); - wx = convertGXtoWX(rangeEnd); - int i1 = findPointBefore(wx); - - if (i1 == i0) { - // set single point at zero so there is nothing played for queueOn() - if (gx < 0) { - setSelection(0, 0); - } - // else clear any existing loop - } else if (i1 == (i0 + 1)) { - setSelection(i1 + 1, i1 + 1); // set to a single point - } else if (i1 > (i0 + 1)) { - setSelection(i0 + 1, i1 + 1); // set to a range of two or more - } - - rangeStart = -1; - rangeEnd = -1; - fireObjectEdited(); - } - - private void clickDownPoints(boolean shiftDown, int gx, int gy) { - dragIndex = -1; - double wx = convertGXtoWX(gx); - double wy = convertGYtoWY(gy); - // calculate world values for aperture - double wxAp = convertGXtoWX(radius + 2) - convertGXtoWX(0); - // System.out.println("wxAp = " + wxAp ); - double wyAp = convertGYtoWY(0) - convertGYtoWY(radius + 2); - // System.out.println("wyAp = " + wyAp ); - int pnt = pickPoint(wx, wxAp, wy, wyAp); - // System.out.println("pickPoint = " + pnt); - if (shiftDown) { - if (pnt >= 0) { - points.removePoint(pnt); - repaint(); - } - } else { - if (pnt < 0) // didn't hit one so look for point to left of click - { - if (points.size() < maxPoints) // add if room - { - pnt = findPointBefore(wx); - // System.out.println("pointBefore = " + pnt); - dragIndex = pnt + 1; - if (pnt == (points.size() - 1)) { - points.add(wx - xBefore, wy); - } else { - points.insert(dragIndex, wx - xBefore, wy); - } - dragLowLimit = xBefore; - dragHighLimit = wx + (maximumXRange - points.getTotalDuration()); - repaint(); - } - } else - // hit one so drag it - { - dragIndex = pnt; - if (dragIndex <= 0) - dragLowLimit = 0.0; // FIXME envelope drag limit - else - dragLowLimit = xPicked - points.getPoint(dragIndex)[0]; - dragHighLimit = xPicked + (maximumXRange - points.getTotalDuration()); - // System.out.println("dragLowLimit = " + dragLowLimit ); - } - } - // Set up drag point if we are dragging. - if (dragIndex >= 0) { - draggedPoint = points.getPoint(dragIndex); - } - - } - - private void dragPoint(int gx, int gy) { - if (dragIndex < 0) - return; - - double wx = convertGXtoWX(gx); - if (wx < dragLowLimit) - wx = dragLowLimit; - else if (wx > dragHighLimit) - wx = dragHighLimit; - draggedPoint[0] = wx - dragLowLimit; // duration - - double wy = convertGYtoWY(gy); - wy = clipWorldY(wy); - draggedPoint[1] = wy; - dragWY = wy; - dragWX = wx; - points.setDirty(true); - repaint(); - } - - private void clickUpPoints(int gx, int gy) { - dragPoint(gx, gy); - fireObjectEdited(); - dragIndex = -1; - } - - // Implement the MouseMotionListener interface for AWT 1.1 - @Override - public void mouseDragged(MouseEvent e) { - int x = e.getX(); - int y = e.getY(); - if (points == null) - return; - if (mode == EDIT_POINTS) { - dragPoint(x, y); - } else { - dragRange(x, y); - } - } - - @Override - public void mouseMoved(MouseEvent e) { - } - - // Implement the MouseListener interface for AWT 1.1 - @Override - public void mousePressed(MouseEvent e) { - int x = e.getX(); - int y = e.getY(); - if (points == null) - return; - if (mode == EDIT_POINTS) { - clickDownPoints(e.isShiftDown(), x, y); - } else { - clickDownRange(e.isShiftDown(), x, y); - } - } - - @Override - public void mouseClicked(MouseEvent e) { - } - - @Override - public void mouseReleased(MouseEvent e) { - int x = e.getX(); - int y = e.getY(); - if (points == null) - return; - if (mode == EDIT_POINTS) { - clickUpPoints(x, y); - } else { - clickUpRange(x, y); - } - } - - @Override - public void mouseEntered(MouseEvent e) { - } - - @Override - public void mouseExited(MouseEvent e) { - } - - /** - * Draw selected range. - */ - private void drawRange(Graphics g) { - if (rangeStart >= 0) { - int height = getHeight(); - int gx0 = 0, gx1 = 0; - - if (rangeEnd < rangeStart) { - gx0 = rangeEnd; - gx1 = rangeStart; - } else { - gx0 = rangeStart; - gx1 = rangeEnd; - } - g.setColor(rangeColor); - g.fillRect(gx0, 0, gx1 - gx0, height); - } - } - - private void drawUnderSelection(Graphics g, int start, int end) { - if (start >= 0) { - int height = getHeight(); - int gx0 = 0, gx1 = radius; - double wx = 0.0; - for (int i = 0; i <= (end - 1); i++) { - double dar[] = (double[]) points.elementAt(i); - wx += dar[0]; - if (start == (i + 1)) { - gx0 = convertWXtoGX(wx) + radius; - } - if (end == (i + 1)) { - gx1 = convertWXtoGX(wx) + radius; - } - } - if (gx0 == gx1) - gx0 = gx0 - radius; - g.fillRect(gx0, 0, gx1 - gx0, height); - } - } - - private void drawSelections(Graphics g) { - int sus0 = points.getSustainBegin(); - int sus1 = points.getSustainEnd(); - int rel0 = points.getReleaseBegin(); - int rel1 = points.getReleaseEnd(); - - g.setColor(sustainColor); - drawUnderSelection(g, sus0, sus1); - g.setColor(releaseColor); - drawUnderSelection(g, rel0, rel1); - // draw overlapping sustain and release region - if (sus1 >= rel0) { - int sel1 = (rel1 < sus1) ? rel1 : sus1; - g.setColor(overlapColor); - drawUnderSelection(g, rel0, sel1); - } - } - - /** - * Override this to draw a grid or other stuff under the envelope. - */ - public void drawUnderlay(Graphics g) { - if (dragIndex < 0) { - drawSelections(g); - drawRange(g); - } - if (verticalBarsEnabled) - drawVerticalBars(g); - } - - public void setVerticalBarsEnabled(boolean flag) { - verticalBarsEnabled = flag; - } - - public boolean areVerticalBarsEnabled() { - return verticalBarsEnabled; - } - - /** - * Set spacing in world coordinates. - */ - public void setVerticalBarSpacing(double spacing) { - verticalBarSpacing = spacing; - } - - public double getVerticalBarSpacing() { - return verticalBarSpacing; - } - - /** - * Draw vertical lines. - */ - private void drawVerticalBars(Graphics g) { - int width = getWidth(); - int height = getHeight(); - double wx = verticalBarSpacing; - int gx; - - // g.setColor( getBackground().darker() ); - g.setColor(Color.lightGray); - while (true) { - gx = convertWXtoGX(wx); - if (gx > width) - break; - g.drawLine(gx, 0, gx, height); - wx += verticalBarSpacing; - } - } - - public void drawPoints(Graphics g, Color lineColor) { - double wx = 0.0; - int gx1 = 0; - int gy1 = getHeight(); - for (int i = 0; i < points.size(); i++) { - double dar[] = (double[]) points.elementAt(i); - wx += dar[0]; - double wy = dar[1]; - int gx2 = convertWXtoGX(wx); - int gy2 = convertWYtoGY(wy); - if (i == 0) { - g.setColor(isEnabled() ? firstLineColor : firstLineColor.darker()); - g.drawLine(gx1, gy1, gx2, gy2); - g.setColor(isEnabled() ? lineColor : lineColor.darker()); - } else if (i > 0) { - g.drawLine(gx1, gy1, gx2, gy2); - } - int diameter = (2 * radius) + 1; - g.fillOval(gx2 - radius, gy2 - radius, diameter, diameter); - gx1 = gx2; - gy1 = gy2; - } - } - - public void drawAllPoints(Graphics g) { - drawPoints(g, getForeground()); - } - - /* Override default paint action. */ - @Override - public void paint(Graphics g) { - double wx = 0.0; - int width = getWidth(); - int height = getHeight(); - - // draw background and erase all values - g.setColor(isEnabled() ? getBackground() : getBackground().darker()); - g.fillRect(0, 0, width, height); - - if (points == null) { - g.setColor(getForeground()); - g.drawString("No EnvelopePoints", 10, 30); - return; - } - - // Determine total duration. - if (points.size() > 0) { - wx = points.getTotalDuration(); - // Adjust max X so that we see entire circle of last point. - double radiusWX = this.convertGXtoWX(radius) - this.getMinWorldX(); - double wxFar = wx + radiusWX; - if (wxFar > getMaxWorldX()) { - if (wx > maximumXRange) - wxFar = maximumXRange; - setMaxWorldX(wxFar); - } else if (wx < (getMaxWorldX() * 0.7)) { - double newMax = wx / 0.7001; // make slightly larger to prevent - // endless jitter, FIXME - still - // needed after repaint() - // removed from setMaxWorldX? - // System.out.println("newMax = " + newMax ); - if (newMax < minimumXRange) - newMax = minimumXRange; - setMaxWorldX(newMax); - } - } - // System.out.println("total X = " + wx ); - - drawUnderlay(g); - - drawAllPoints(g); - - /* Show X,Y,TotalX as text. */ - g.drawString(points.getName() + ", len=" + String.format("%7.3f", wx), 5, 15); - if ((draggedPoint != null) && (dragIndex >= 0)) { - String s = "i=" + dragIndex + ", dur=" - + String.format("%7.3f", draggedPoint[0]) + ", y = " - + String.format("%8.4f", draggedPoint[1]); - g.drawString(s, 5, 30); - } - } -} diff --git a/src/com/jsyn/swing/EnvelopeEditorPanel.java b/src/com/jsyn/swing/EnvelopeEditorPanel.java deleted file mode 100644 index dc9f2cd..0000000 --- a/src/com/jsyn/swing/EnvelopeEditorPanel.java +++ /dev/null @@ -1,164 +0,0 @@ -/* - * 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.swing; - -import java.awt.BorderLayout; -import java.awt.Button; -import java.awt.Checkbox; -import java.awt.CheckboxGroup; -import java.awt.Color; -import java.awt.Dimension; -import java.awt.Label; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.awt.event.ItemEvent; -import java.awt.event.ItemListener; - -import javax.swing.JPanel; -import javax.swing.event.ChangeEvent; -import javax.swing.event.ChangeListener; - -public class EnvelopeEditorPanel extends JPanel { - EnvelopeEditorBox editor; - Checkbox pointsBox; - Checkbox sustainBox; - Checkbox releaseBox; - Checkbox autoBox; - Button onButton; - Button offButton; - Button clearButton; - Button yUpButton; - Button yDownButton; - DoubleBoundedTextField zoomField; - - public EnvelopeEditorPanel(EnvelopePoints points, int maxFrames) { - setSize(600, 300); - - setLayout(new BorderLayout()); - editor = new EnvelopeEditorBox(); - editor.setMaxPoints(maxFrames); - editor.setBackground(Color.cyan); - editor.setPoints(points); - editor.setMinimumSize(new Dimension(500, 300)); - - add(editor, "Center"); - - JPanel buttonPanel = new JPanel(); - add(buttonPanel, "South"); - - CheckboxGroup cbg = new CheckboxGroup(); - pointsBox = new Checkbox("points", cbg, true); - pointsBox.addItemListener(new ItemListener() { - @Override - public void itemStateChanged(ItemEvent e) { - editor.setMode(EnvelopeEditorBox.EDIT_POINTS); - } - }); - buttonPanel.add(pointsBox); - - sustainBox = new Checkbox("onLoop", cbg, false); - sustainBox.addItemListener(new ItemListener() { - @Override - public void itemStateChanged(ItemEvent e) { - editor.setMode(EnvelopeEditorBox.SELECT_SUSTAIN); - } - }); - buttonPanel.add(sustainBox); - - releaseBox = new Checkbox("offLoop", cbg, false); - releaseBox.addItemListener(new ItemListener() { - @Override - public void itemStateChanged(ItemEvent e) { - editor.setMode(EnvelopeEditorBox.SELECT_RELEASE); - } - }); - buttonPanel.add(releaseBox); - - autoBox = new Checkbox("AutoStop", false); - /* - * buttonPanel.add( onButton = new Button( "On" ) ); onButton.addActionListener( module ); - * buttonPanel.add( offButton = new Button( "Off" ) ); offButton.addActionListener( module - * ); buttonPanel.add( clearButton = new Button( "Clear" ) ); clearButton.addActionListener( - * module ); - */ - buttonPanel.add(yUpButton = new Button("Y*2")); - yUpButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - scaleEnvelopeValues(2.0); - } - }); - - buttonPanel.add(yDownButton = new Button("Y/2")); - yDownButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - scaleEnvelopeValues(0.5); - } - }); - - /* Add a TextField for setting the Y scale. */ - double max = getMaxEnvelopeValue(editor.getPoints()); - editor.setMaxWorldY(max); - buttonPanel.add(new Label("YMax =")); - final DoubleBoundedRangeModel model = new DoubleBoundedRangeModel("YMax", 100000, 1.0, - 100001.0, 1.0); - buttonPanel.add(zoomField = new DoubleBoundedTextField(model, 8)); - model.addChangeListener(new ChangeListener() { - @Override - public void stateChanged(ChangeEvent e) { - try { - double val = model.getDoubleValue(); - editor.setMaxWorldY(val); - editor.repaint(); - } catch (NumberFormatException exp) { - zoomField.setText("ERROR"); - zoomField.selectAll(); - } - } - }); - - validate(); - } - - /** - * Multiply all the values in the envelope by scalar. - */ - double getMaxEnvelopeValue(EnvelopePoints points) { - double max = 1.0; - for (int i = 0; i < points.size(); i++) { - double value = points.getValue(i); - if (value > max) { - max = value; - } - } - return max; - } - - /** - * Multiply all the values in the envelope by scalar. - */ - void scaleEnvelopeValues(double scalar) { - EnvelopePoints points = editor.getPoints(); - for (int i = 0; i < points.size(); i++) { - double[] dar = points.getPoint(i); - dar[1] = dar[1] * scalar; // scale value - } - points.setDirty(true); - editor.repaint(); - } -} diff --git a/src/com/jsyn/swing/EnvelopePoints.java b/src/com/jsyn/swing/EnvelopePoints.java deleted file mode 100644 index ab4ed03..0000000 --- a/src/com/jsyn/swing/EnvelopePoints.java +++ /dev/null @@ -1,234 +0,0 @@ -/* - * Copyright 1997 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.swing; - -import java.util.Vector; - -import com.jsyn.data.SegmentedEnvelope; - -/** - * Vector that contains duration,value pairs. Used by EnvelopeEditor - * - * @author (C) 1997 Phil Burk, SoftSynth.com - */ - -/* ========================================================================== */ -public class EnvelopePoints extends Vector { - private String name = ""; - private double maximumValue = 1.0; - private int sustainBegin = -1; - private int sustainEnd = -1; - private int releaseBegin = -1; - private int releaseEnd = -1; - private boolean dirty = false; - - /** - * Update only if points or loops were modified. - */ - public void updateEnvelopeIfDirty(SegmentedEnvelope envelope) { - if (dirty) { - updateEnvelope(envelope); - } - } - - /** - * The editor works on a vector of points, not a real envelope. The data must be written to a - * real SynthEnvelope in order to use it. - */ - public void updateEnvelope(SegmentedEnvelope envelope) { - int numFrames = size(); - for (int i = 0; i < numFrames; i++) { - envelope.write(i, getPoint(i), 0, 1); - } - envelope.setSustainBegin(getSustainBegin()); - envelope.setSustainEnd(getSustainEnd()); - envelope.setReleaseBegin(getReleaseBegin()); - envelope.setReleaseEnd(getReleaseEnd()); - envelope.setNumFrames(numFrames); - dirty = false; - } - - public void setName(String name) { - this.name = name; - } - - public String getName() { - return name; - } - - public void setMaximumValue(double maximumValue) { - this.maximumValue = maximumValue; - } - - public double getMaximumValue() { - return maximumValue; - } - - public void add(double dur, double value) { - double dar[] = { - dur, value - }; - addElement(dar); - dirty = true; - } - - /** - * Insert point without changing total duration by reducing next points duration. - */ - public void insert(int index, double dur, double y) { - double dar[] = { - dur, y - }; - if (index < size()) { - ((double[]) elementAt(index))[0] -= dur; - } - insertElementAt(dar, index); - - if (index <= sustainBegin) - sustainBegin += 1; - if (index <= sustainEnd) - sustainEnd += 1; - if (index <= releaseBegin) - releaseBegin += 1; - if (index <= releaseEnd) - releaseEnd += 1; - dirty = true; - } - - /** - * Remove indexed point and update sustain and release loops if necessary. Did not name this - * "remove()" because of conflicts with new JDK 1.3 method with the same name. - */ - public void removePoint(int index) { - super.removeElementAt(index); - // move down loop if points below or inside loop removed - if (index < sustainBegin) - sustainBegin -= 1; - if (index <= sustainEnd) - sustainEnd -= 1; - if (index < releaseBegin) - releaseBegin -= 1; - if (index <= releaseEnd) - releaseEnd -= 1; - - // was entire loop removed? - if (sustainBegin > sustainEnd) { - sustainBegin = -1; - sustainEnd = -1; - } - // was entire loop removed? - if (releaseBegin > releaseEnd) { - releaseBegin = -1; - releaseEnd = -1; - } - dirty = true; - } - - public double getDuration(int index) { - return ((double[]) elementAt(index))[0]; - } - - public double getValue(int index) { - return ((double[]) elementAt(index))[1]; - } - - public double[] getPoint(int index) { - return (double[]) elementAt(index); - } - - public double getTotalDuration() { - double sum = 0.0; - for (int i = 0; i < size(); i++) { - double dar[] = (double[]) elementAt(i); - sum += dar[0]; - } - return sum; - } - - /** - * Set location of Sustain Loop in units of Frames. Set SustainBegin to -1 if no Sustain Loop. - * SustainEnd value is the frame index of the frame just past the end of the loop. The number of - * frames included in the loop is (SustainEnd - SustainBegin). - */ - public void setSustainLoop(int startFrame, int endFrame) { - this.sustainBegin = startFrame; - this.sustainEnd = endFrame; - dirty = true; - } - - /*** - * @return Beginning of sustain loop or -1 if no loop. - */ - public int getSustainBegin() { - return this.sustainBegin; - } - - /*** - * @return End of sustain loop or -1 if no loop. - */ - public int getSustainEnd() { - return this.sustainEnd; - } - - /*** - * @return Size of sustain loop in frames, 0 if no loop. - */ - public int getSustainSize() { - return (this.sustainEnd - this.sustainBegin); - } - - /** - * Set location of Release Loop in units of Frames. Set ReleaseBegin to -1 if no ReleaseLoop. - * ReleaseEnd value is the frame index of the frame just past the end of the loop. The number of - * frames included in the loop is (ReleaseEnd - ReleaseBegin). - */ - public void setReleaseLoop(int startFrame, int endFrame) { - this.releaseBegin = startFrame; - this.releaseEnd = endFrame; - dirty = true; - } - - /*** - * @return Beginning of release loop or -1 if no loop. - */ - public int getReleaseBegin() { - return this.releaseBegin; - } - - /*** - * @return End of release loop or -1 if no loop. - */ - public int getReleaseEnd() { - return this.releaseEnd; - } - - /*** - * @return Size of release loop in frames, 0 if no loop. - */ - public int getReleaseSize() { - return (this.releaseEnd - this.releaseBegin); - } - - public boolean isDirty() { - return dirty; - } - - public void setDirty(boolean b) { - dirty = b; - } - -} diff --git a/src/com/jsyn/swing/ExponentialRangeModel.java b/src/com/jsyn/swing/ExponentialRangeModel.java deleted file mode 100644 index 4411947..0000000 --- a/src/com/jsyn/swing/ExponentialRangeModel.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright 2011 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.swing; - -/** - * Maps integer range info to a double value along an exponential scale. - * - *
- * 
- *   x = ival / resolution
- *   f(x) = a*(rootˆcx) + b
- *   f(0.0) = dmin
- *   f(1.0) = dmax
- *   b = dmin - a
- *   a = (dmax - dmin) / (rootˆc - 1)
- *  
- *   Inverse function:
- *   x = log( (y-b)/a ) / log(root)
- * 
- * 
- * - * @author Phil Burk, (C) 2011 Mobileer Inc - */ -public class ExponentialRangeModel extends DoubleBoundedRangeModel { - private static final long serialVersionUID = -142785624892302160L; - double a = 1.0; - double b = -1.0; - double span = 1.0; - double root = 10.0; - - /** Use default root of 10.0 and span of 1.0. */ - public ExponentialRangeModel(String name, int resolution, double dmin, double dmax, double dval) { - this(name, resolution, dmin, dmax, dval, 1.0); - } - - /** Set span before setting double value so it is translated correctly. */ - ExponentialRangeModel(String name, int resolution, double dmin, double dmax, double dval, - double span) { - super(name, resolution, dmin, dmax, dval); - setRoot(10.0); - setSpan(span); - /* Set again after coefficients setup. */ - setDoubleValue(dval); - } - - private void updateCoefficients() { - a = (getDoubleMaximum() - getDoubleMinimum()) / (Math.pow(root, span) - 1.0); - b = getDoubleMinimum() - a; - } - - private void setRoot(double w) { - root = w; - updateCoefficients(); - } - - public double getRoot() { - return root; - } - - public void setSpan(double c) { - this.span = c; - updateCoefficients(); - } - - public double getSpan() { - return span; - } - - @Override - public double sliderToDouble(int sliderValue) { - updateCoefficients(); // TODO optimize when we call this - double x = (double) sliderValue / getMaximum(); - double y = (a * Math.pow(root, span * x)) + b; - return y; - } - - @Override - public int doubleToSlider(double dval) { - updateCoefficients(); // TODO optimize when we call this - double z = (dval - b) / a; - double x = Math.log(z) / (span * Math.log(root)); - return (int) Math.round(x * getMaximum()); - } - - public void test(int sliderValue) { - double dval = sliderToDouble(sliderValue); - int ival = doubleToSlider(dval); - System.out.println(sliderValue + " => " + dval + " => " + ival); - } - -} diff --git a/src/com/jsyn/swing/InstrumentBrowser.java b/src/com/jsyn/swing/InstrumentBrowser.java deleted file mode 100644 index 55575a3..0000000 --- a/src/com/jsyn/swing/InstrumentBrowser.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright 2012 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.swing; - -import java.awt.Dimension; -import java.awt.GridLayout; -import java.util.ArrayList; - -import javax.swing.JList; -import javax.swing.JPanel; -import javax.swing.JScrollPane; -import javax.swing.ListSelectionModel; -import javax.swing.event.ListSelectionEvent; -import javax.swing.event.ListSelectionListener; - -import com.jsyn.util.InstrumentLibrary; -import com.jsyn.util.VoiceDescription; - -/** - * Display a list of VoiceDescriptions and their associated presets. Notify PresetSelectionListeners - * when a preset is selected. - * - * @author Phil Burk (C) 2012 Mobileer Inc - */ -@SuppressWarnings("serial") -public class InstrumentBrowser extends JPanel { - private InstrumentLibrary library; - private JScrollPane listScroller2; - private VoiceDescription voiceDescription; - private ArrayList listeners = new ArrayList(); - - public InstrumentBrowser(InstrumentLibrary library) { - this.library = library; - JPanel horizontalPanel = new JPanel(); - horizontalPanel.setLayout(new GridLayout(1, 2)); - - final JList instrumentList = new JList(library.getVoiceDescriptions()); - setupList(instrumentList); - instrumentList.addListSelectionListener(new ListSelectionListener() { - @Override - public void valueChanged(ListSelectionEvent e) { - if (e.getValueIsAdjusting() == false) { - int n = instrumentList.getSelectedIndex(); - if (n >= 0) { - showPresetList(n); - } - } - } - }); - - JScrollPane listScroller1 = new JScrollPane(instrumentList); - listScroller1.setPreferredSize(new Dimension(250, 120)); - add(listScroller1); - - instrumentList.setSelectedIndex(0); - } - - public void addPresetSelectionListener(PresetSelectionListener listener) { - listeners.add(listener); - } - - public void removePresetSelectionListener(PresetSelectionListener listener) { - listeners.remove(listener); - } - - private void firePresetSelectionListeners(VoiceDescription voiceDescription, int presetIndex) { - for (PresetSelectionListener listener : listeners) { - listener.presetSelected(voiceDescription, presetIndex); - } - } - - private void showPresetList(int n) { - if (listScroller2 != null) { - remove(listScroller2); - } - voiceDescription = library.getVoiceDescriptions()[n]; - final JList presetList = new JList(voiceDescription.getPresetNames()); - setupList(presetList); - presetList.addListSelectionListener(new ListSelectionListener() { - @Override - public void valueChanged(ListSelectionEvent e) { - if (e.getValueIsAdjusting() == false) { - int n = presetList.getSelectedIndex(); - if (n >= 0) { - firePresetSelectionListeners(voiceDescription, n); - } - } - } - }); - - listScroller2 = new JScrollPane(presetList); - listScroller2.setPreferredSize(new Dimension(250, 120)); - add(listScroller2); - presetList.setSelectedIndex(0); - validate(); - } - - private void setupList(@SuppressWarnings("rawtypes") JList list) { - list.setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION); - list.setLayoutOrientation(JList.VERTICAL); - list.setVisibleRowCount(-1); - } -} diff --git a/src/com/jsyn/swing/JAppletFrame.java b/src/com/jsyn/swing/JAppletFrame.java deleted file mode 100644 index 53bd65b..0000000 --- a/src/com/jsyn/swing/JAppletFrame.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 1997 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.swing; - -import java.awt.event.WindowAdapter; -import java.awt.event.WindowEvent; - -import javax.swing.JApplet; -import javax.swing.JFrame; - -/** - * Frame that allows a program to be run as either an Application or an Applet. Used by JSyn example - * programs. - * - * @author (C) 1997 Phil Burk, SoftSynth.com - */ - -public class JAppletFrame extends JFrame { - private static final long serialVersionUID = -6047247494856379114L; - JApplet applet; - - public JAppletFrame(String frameTitle, final JApplet pApplet) { - super(frameTitle); - this.applet = pApplet; - getContentPane().add(applet); - repaint(); - - addWindowListener(new WindowAdapter() { - @Override - public void windowClosing(WindowEvent e) { - applet.stop(); - applet.destroy(); - try { - System.exit(0); - } catch (SecurityException exc) { - System.err.println("System.exit(0) not allowed by Java VM."); - } - } - - @Override - public void windowClosed(WindowEvent e) { - } - }); - } - - public void test() { - applet.init(); - applet.start(); - } - -} diff --git a/src/com/jsyn/swing/PortBoundedRangeModel.java b/src/com/jsyn/swing/PortBoundedRangeModel.java deleted file mode 100644 index a5cf841..0000000 --- a/src/com/jsyn/swing/PortBoundedRangeModel.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2011 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.swing; - -import javax.swing.event.ChangeEvent; -import javax.swing.event.ChangeListener; - -import com.jsyn.ports.UnitInputPort; - -/** - * A bounded range model that drives a UnitInputPort. The range of the model is set based on the min - * and max of the port. - * - * @author Phil Burk (C) 2011 Mobileer Inc - */ -public class PortBoundedRangeModel extends DoubleBoundedRangeModel { - private static final long serialVersionUID = -8011867146560305808L; - private UnitInputPort port; - - public PortBoundedRangeModel(UnitInputPort pPort) { - super(pPort.getName(), 10000, pPort.getMinimum(), pPort.getMaximum(), pPort.getValue()); - this.port = pPort; - addChangeListener(new ChangeListener() { - @Override - public void stateChanged(ChangeEvent e) { - port.set(getDoubleValue()); - } - }); - } - -} diff --git a/src/com/jsyn/swing/PortControllerFactory.java b/src/com/jsyn/swing/PortControllerFactory.java deleted file mode 100644 index a73d047..0000000 --- a/src/com/jsyn/swing/PortControllerFactory.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright 2010 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.swing; - -import javax.swing.event.ChangeEvent; -import javax.swing.event.ChangeListener; - -import com.jsyn.ports.UnitInputPort; - -/** - * Factory class for making various controllers for JSyn ports. - * - * @author Phil Burk (C) 2010 Mobileer Inc - */ -public class PortControllerFactory { - private static final int RESOLUTION = 100000; - - public static DoubleBoundedRangeSlider createPortSlider(final UnitInputPort port) { - DoubleBoundedRangeModel rangeModel = new DoubleBoundedRangeModel(port.getName(), - RESOLUTION, port.getMinimum(), port.getMaximum(), port.get()); - rangeModel.addChangeListener(new ChangeListener() { - @Override - public void stateChanged(ChangeEvent e) { - DoubleBoundedRangeModel model = (DoubleBoundedRangeModel) e.getSource(); - double value = model.getDoubleValue(); - port.set(value); - } - }); - return new DoubleBoundedRangeSlider(rangeModel, 4); - } - - public static DoubleBoundedRangeSlider createExponentialPortSlider(final UnitInputPort port) { - ExponentialRangeModel rangeModel = new ExponentialRangeModel(port.getName(), RESOLUTION, - port.getMinimum(), port.getMaximum(), port.get()); - rangeModel.addChangeListener(new ChangeListener() { - @Override - public void stateChanged(ChangeEvent e) { - ExponentialRangeModel model = (ExponentialRangeModel) e.getSource(); - double value = model.getDoubleValue(); - port.set(value); - } - }); - return new DoubleBoundedRangeSlider(rangeModel, 4); - } - -} diff --git a/src/com/jsyn/swing/PortModelFactory.java b/src/com/jsyn/swing/PortModelFactory.java deleted file mode 100644 index 8bec76a..0000000 --- a/src/com/jsyn/swing/PortModelFactory.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * 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.swing; - -import javax.swing.event.ChangeEvent; -import javax.swing.event.ChangeListener; - -import com.jsyn.ports.UnitInputPort; - -public class PortModelFactory { - private static final int RESOLUTION = 1000000; - - public static DoubleBoundedRangeModel createLinearModel(final UnitInputPort pPort) { - final DoubleBoundedRangeModel model = new DoubleBoundedRangeModel(pPort.getName(), - RESOLUTION, pPort.getMinimum(), pPort.getMaximum(), pPort.get()); - model.addChangeListener(new ChangeListener() { - @Override - public void stateChanged(ChangeEvent e) { - pPort.set(model.getDoubleValue()); - } - }); - return model; - } - - public static ExponentialRangeModel createExponentialModel(final UnitInputPort pPort) { - final ExponentialRangeModel model = new ExponentialRangeModel(pPort.getName(), RESOLUTION, - pPort.getMinimum(), pPort.getMaximum(), pPort.get()); - model.addChangeListener(new ChangeListener() { - @Override - public void stateChanged(ChangeEvent e) { - pPort.set(model.getDoubleValue()); - } - }); - return model; - } - - public static ExponentialRangeModel createExponentialModel(final int partNum, - final UnitInputPort pPort) { - final ExponentialRangeModel model = new ExponentialRangeModel(pPort.getName(), RESOLUTION, - pPort.getMinimum(), pPort.getMaximum(), pPort.get()); - model.addChangeListener(new ChangeListener() { - @Override - public void stateChanged(ChangeEvent e) { - pPort.set(partNum, model.getDoubleValue()); - } - }); - return model; - } - -} diff --git a/src/com/jsyn/swing/PresetSelectionListener.java b/src/com/jsyn/swing/PresetSelectionListener.java deleted file mode 100644 index daf0310..0000000 --- a/src/com/jsyn/swing/PresetSelectionListener.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * 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.swing; - -import com.jsyn.util.VoiceDescription; - -public interface PresetSelectionListener { - public void presetSelected(VoiceDescription voiceDescription, int presetIndex); -} diff --git a/src/com/jsyn/swing/RotaryController.java b/src/com/jsyn/swing/RotaryController.java deleted file mode 100644 index c26c37f..0000000 --- a/src/com/jsyn/swing/RotaryController.java +++ /dev/null @@ -1,335 +0,0 @@ -/* - * Copyright 2010 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.swing; - -import java.awt.Color; -import java.awt.Dimension; -import java.awt.Graphics; -import java.awt.Graphics2D; -import java.awt.RenderingHints; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; -import java.awt.event.MouseMotionAdapter; - -import javax.swing.BoundedRangeModel; -import javax.swing.JPanel; -import javax.swing.event.ChangeEvent; -import javax.swing.event.ChangeListener; - -/** - * Rotary controller looks like a knob on a synthesizer. You control this knob by clicking on it and - * dragging up or down. If you move the mouse to the left of the knob then you - * will have coarse control. If you move the mouse to the right of the knob then you - * will have fine control. - *

- * - * @author (C) 2010 Phil Burk, Mobileer Inc - * @version 16.1 - */ -public class RotaryController extends JPanel { - private static final long serialVersionUID = 6681532871556659546L; - private static final double SENSITIVITY = 0.01; - private final BoundedRangeModel model; - - private final double minAngle = 1.4 * Math.PI; - private final double maxAngle = -0.4 * Math.PI; - private final double unitIncrement = 0.01; - private int lastY; - private int startX; - private Color knobColor = Color.LIGHT_GRAY; - private Color lineColor = Color.RED; - private double baseValue; - - public enum Style { - LINE, LINEDOT, ARROW, ARC - }; - - private Style style = Style.ARC; - - public RotaryController(BoundedRangeModel model) { - this.model = model; - setMinimumSize(new Dimension(50, 50)); - setPreferredSize(new Dimension(50, 50)); - addMouseListener(new MouseHandler()); - addMouseMotionListener(new MouseMotionHandler()); - model.addChangeListener(new ChangeListener() { - @Override - public void stateChanged(ChangeEvent e) { - safeRepaint(); - } - - }); - } - - // This can be overridden in subclasses to workaround OpenJDK bugs. - public void safeRepaint() { - repaint(); - } - - public BoundedRangeModel getModel() { - return model; - } - - private class MouseHandler extends MouseAdapter { - - @Override - public void mousePressed(MouseEvent e) { - lastY = e.getY(); - startX = e.getX(); - } - - @Override - public void mouseReleased(MouseEvent e) { - if (isEnabled()) { - setKnobByXY(e.getX(), e.getY()); - } - } - } - - private class MouseMotionHandler extends MouseMotionAdapter { - @Override - public void mouseDragged(MouseEvent e) { - if (isEnabled()) { - setKnobByXY(e.getX(), e.getY()); - } - } - } - - private int getModelRange() { - return (((model.getMaximum() - model.getExtent()) - model.getMinimum())); - } - - /** - * A fractional value is useful for drawing. - * - * @return model value as a normalized fraction between 0.0 and 1.0 - */ - public double getFractionFromModel() { - double value = model.getValue(); - return convertValueToFraction(value); - } - - private double convertValueToFraction(double value) { - return (value - model.getMinimum()) / getModelRange(); - } - - private void setKnobByXY(int x, int y) { - // Scale increment by X position. - int xdiff = startX - x; // More to left causes bigger increments. - double power = xdiff * SENSITIVITY; - double perPixel = unitIncrement * Math.pow(2.0, power); - - int ydiff = lastY - y; - double fractionalDelta = ydiff * perPixel; - // Only update the model if we actually change values. - // This is needed in case the range is small. - int valueDelta = (int) Math.round(fractionalDelta * getModelRange()); - if (valueDelta != 0) { - model.setValue(model.getValue() + valueDelta); - lastY = y; - } - } - - private double fractionToAngle(double fraction) { - return (fraction * (maxAngle - minAngle)) + minAngle; - } - - private void drawLineIndicator(Graphics g, int x, int y, int radius, double angle, - boolean drawDot) { - double arrowSize = radius * 0.95; - int arrowX = (int) (arrowSize * Math.sin(angle)); - int arrowY = (int) (arrowSize * Math.cos(angle)); - g.setColor(lineColor); - g.drawLine(x, y, x + arrowX, y - arrowY); - if (drawDot) { - // draw little dot at end - double dotScale = 0.1; - int dotRadius = (int) (dotScale * arrowSize); - if (dotRadius > 1) { - int dotX = x + (int) ((0.99 - dotScale) * arrowX) - dotRadius; - int dotY = y - (int) ((0.99 - dotScale) * arrowY) - dotRadius; - g.fillOval(dotX, dotY, dotRadius * 2, dotRadius * 2); - } - } - } - - private void drawArrowIndicator(Graphics g, int x0, int y0, int radius, double angle) { - int arrowSize = (int) (radius * 0.95); - int arrowWidth = (int) (radius * 0.2); - int xp[] = { - 0, arrowWidth, 0, -arrowWidth - }; - int yp[] = { - arrowSize, -arrowSize / 2, 0, -arrowSize / 2 - }; - double sa = Math.sin(angle); - double ca = Math.cos(angle); - for (int i = 0; i < xp.length; i++) { - int x = xp[i]; - int y = yp[i]; - xp[i] = x0 - (int) ((x * ca) - (y * sa)); - yp[i] = y0 - (int) ((x * sa) + (y * ca)); - } - g.fillPolygon(xp, yp, xp.length); - } - - private void drawArcIndicator(Graphics g, int x, int y, int radius, double angle) { - final double DEGREES_PER_RADIAN = 180.0 / Math.PI; - final int minAngleDegrees = (int) (minAngle * DEGREES_PER_RADIAN); - final int maxAngleDegrees = (int) (maxAngle * DEGREES_PER_RADIAN); - - int zeroAngleDegrees = (int) (fractionToAngle(baseValue) * DEGREES_PER_RADIAN); - - double arrowSize = radius * 0.95; - int arcX = x - radius; - int arcY = y - radius; - int arcAngle = (int) (angle * DEGREES_PER_RADIAN); - int arrowX = (int) (arrowSize * Math.cos(angle)); - int arrowY = (int) (arrowSize * Math.sin(angle)); - - g.setColor(knobColor.darker().darker()); - g.fillArc(arcX, arcY, 2 * radius, 2 * radius, minAngleDegrees, maxAngleDegrees - - minAngleDegrees); - g.setColor(Color.ORANGE); - g.fillArc(arcX, arcY, 2 * radius, 2 * radius, zeroAngleDegrees, arcAngle - zeroAngleDegrees); - - // fill in middle - int arcWidth = radius / 4; - int diameter = ((radius - arcWidth) * 2); - g.setColor(knobColor); - g.fillOval(arcWidth + x - radius, arcWidth + y - radius, diameter, diameter); - - g.setColor(lineColor); - g.drawLine(x, y, x + arrowX, y - arrowY); - - } - - /** - * Override this method if you want to draw your own line or dot on the knob. - */ - public void drawIndicator(Graphics g, int x, int y, int radius, double angle) { - g.setColor(isEnabled() ? lineColor : lineColor.darker()); - switch (style) { - case LINE: - drawLineIndicator(g, x, y, radius, angle, false); - break; - case LINEDOT: - drawLineIndicator(g, x, y, radius, angle, true); - break; - case ARROW: - drawArrowIndicator(g, x, y, radius, angle); - break; - case ARC: - drawArcIndicator(g, x, y, radius, angle); - break; - } - } - - /** - * Override this method if you want to draw your own knob. - * - * @param g graphics context - * @param x position of center of knob - * @param y position of center of knob - * @param radius of knob in pixels - * @param angle in radians. Zero is straight up. - */ - public void drawKnob(Graphics g, int x, int y, int radius, double angle) { - Graphics2D g2 = (Graphics2D) g; - g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); - - int diameter = radius * 2; - // Draw shaded side. - g.setColor(knobColor.darker()); - g.fillOval(x - radius + 2, y - radius + 2, diameter, diameter); - g.setColor(knobColor); - g.fillOval(x - radius, y - radius, diameter, diameter); - - // Draw line or other indicator of knob position. - drawIndicator(g, x, y, radius, angle); - } - - // Draw the round knob based on the current size and model value. - // This used to have a bug where the scope would draw in this components background. - // Then I changed it from overriding paint() to overriding paintComponent() and it worked. - @Override - public void paintComponent(Graphics g) { - super.paintComponent(g); - - int width = getWidth(); - int height = getHeight(); - int x = width / 2; - int y = height / 2; - - // Calculate radius from size of component. - int diameter = (width < height) ? width : height; - diameter -= 4; - int radius = diameter / 2; - - double angle = fractionToAngle(getFractionFromModel()); - drawKnob(g, x, y, radius, angle); - } - - public Color getKnobColor() { - return knobColor; - } - - /** - * @param knobColor color of body of knob - */ - public void setKnobColor(Color knobColor) { - this.knobColor = knobColor; - } - - public Color getLineColor() { - return lineColor; - } - - /** - * @param lineColor color of indicator on knob like a line or arrow - */ - public void setLineColor(Color lineColor) { - this.lineColor = lineColor; - } - - public void setStyle(Style style) { - this.style = style; - } - - public Style getStyle() { - return style; - } - - public double getBaseValue() { - return baseValue; - } - - /* - * Specify where the orange arc originates. For example a pan knob with a centered arc would - * have a baseValue of 0.5. - * @param baseValue a fraction between 0.0 and 1.0. - */ - public void setBaseValue(double baseValue) { - if (baseValue < 0.0) { - baseValue = 0.0; - } else if (baseValue > 1.0) { - baseValue = 1.0; - } - this.baseValue = baseValue; - } - -} diff --git a/src/com/jsyn/swing/RotaryTextController.java b/src/com/jsyn/swing/RotaryTextController.java deleted file mode 100644 index 81d6614..0000000 --- a/src/com/jsyn/swing/RotaryTextController.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2010 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.swing; - -import java.awt.BorderLayout; - -import javax.swing.BorderFactory; -import javax.swing.JPanel; - -/** - * Combine a RotaryController and a DoubleBoundedTextField into a convenient package. - * - * @author Phil Burk (C) 2010 Mobileer Inc - */ -public class RotaryTextController extends JPanel { - private static final long serialVersionUID = -2931828326251895375L; - private RotaryController rotary; - private DoubleBoundedTextField textField; - - public RotaryTextController(DoubleBoundedRangeModel pModel, int numDigits) { - rotary = new RotaryController(pModel); - textField = new DoubleBoundedTextField(pModel, numDigits); - setLayout(new BorderLayout()); - add(rotary, BorderLayout.CENTER); - add(textField, BorderLayout.SOUTH); - } - - /** Display the title in a border. */ - public void setTitle(String label) { - setBorder(BorderFactory.createTitledBorder(label)); - } - - @Override - public void setEnabled(boolean enabled) { - super.setEnabled(enabled); - rotary.setEnabled(enabled); - textField.setEnabled(enabled); - } -} diff --git a/src/com/jsyn/swing/SoundTweaker.java b/src/com/jsyn/swing/SoundTweaker.java deleted file mode 100644 index 043677e..0000000 --- a/src/com/jsyn/swing/SoundTweaker.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * 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.swing; - -import java.awt.Component; -import java.awt.GridLayout; -import java.util.ArrayList; -import java.util.logging.Logger; - -import javax.swing.JLabel; -import javax.swing.JPanel; - -import com.jsyn.Synthesizer; -import com.jsyn.ports.UnitInputPort; -import com.jsyn.ports.UnitPort; -import com.jsyn.unitgen.UnitGenerator; -import com.jsyn.unitgen.UnitSource; -import com.jsyn.unitgen.UnitVoice; -import com.jsyn.util.Instrument; -import com.softsynth.math.AudioMath; - -@SuppressWarnings("serial") -public class SoundTweaker extends JPanel { - private UnitSource source; - private ASCIIMusicKeyboard keyboard; - private Synthesizer synth; - - static Logger logger = Logger.getLogger(SoundTweaker.class.getName()); - - public SoundTweaker(Synthesizer synth, String title, UnitSource source) { - this.synth = synth; - this.source = source; - - setLayout(new GridLayout(0, 2)); - - UnitGenerator ugen = source.getUnitGenerator(); - ArrayList sliders = new ArrayList(); - - add(new JLabel(title)); - - if (source instanceof Instrument) { - add(keyboard = createPolyphonicKeyboard()); - } else if (source instanceof UnitVoice) { - add(keyboard = createMonophonicKeyboard()); - } - - // Arrange the faders in a stack. - // Iterate through the ports. - for (UnitPort port : ugen.getPorts()) { - if (port instanceof UnitInputPort) { - UnitInputPort inputPort = (UnitInputPort) port; - Component slider; - // Use an exponential slider if it seems appropriate. - if ((inputPort.getMinimum() > 0.0) - && ((inputPort.getMaximum() / inputPort.getMinimum()) > 4.0)) { - slider = PortControllerFactory.createExponentialPortSlider(inputPort); - } else { - slider = PortControllerFactory.createPortSlider(inputPort); - - } - add(slider); - sliders.add(slider); - } - } - - if (keyboard != null) { - for (Component slider : sliders) { - slider.addKeyListener(keyboard.getKeyListener()); - } - } - validate(); - } - - @SuppressWarnings("serial") - private ASCIIMusicKeyboard createPolyphonicKeyboard() { - ASCIIMusicKeyboard keyboard = new ASCIIMusicKeyboard() { - @Override - public void keyOff(int pitch) { - ((Instrument) source).noteOff(pitch, synth.createTimeStamp()); - } - - @Override - public void keyOn(int pitch) { - double freq = AudioMath.pitchToFrequency(pitch); - ((Instrument) source).noteOn(pitch, freq, 0.5, synth.createTimeStamp()); - } - }; - return keyboard; - } - - @SuppressWarnings("serial") - private ASCIIMusicKeyboard createMonophonicKeyboard() { - ASCIIMusicKeyboard keyboard = new ASCIIMusicKeyboard() { - @Override - public void keyOff(int pitch) { - ((UnitVoice) source).noteOff(synth.createTimeStamp()); - } - - @Override - public void keyOn(int pitch) { - double freq = AudioMath.pitchToFrequency(pitch); - ((UnitVoice) source).noteOn(freq, 0.5, synth.createTimeStamp()); - } - }; - return keyboard; - } - -} diff --git a/src/com/jsyn/swing/XYController.java b/src/com/jsyn/swing/XYController.java deleted file mode 100644 index 0d97c62..0000000 --- a/src/com/jsyn/swing/XYController.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright 1997 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.swing; - -import javax.swing.JPanel; - -/** - * Root class for 2 dimensional X,Y controller for wave editors, Theremins, etc. Maps pixel - * coordinates into "world" coordinates. - * - * @author (C) 1997 Phil Burk, SoftSynth.com - */ - -public class XYController extends JPanel { - double minWorldX = 0.0; - double maxWorldX = 1.0; - double minWorldY = 0.0; - double maxWorldY = 1.0; - - public XYController() { - } - - public XYController(double minWX, double minWY, double maxWX, double maxWY) { - setMinWorldX(minWX); - setMaxWorldX(maxWX); - setMinWorldY(minWY); - setMaxWorldY(maxWY); - } - - /** - * Set minimum World coordinate value for the horizontal X dimension. The minimum value - * corresponds to the left of the component. - */ - public void setMinWorldX(double minWX) { - minWorldX = minWX; - } - - public double getMinWorldX() { - return minWorldX; - } - - /** - * Set maximum World coordinate value for the horizontal X dimension. The minimum value - * corresponds to the right of the component. - */ - public void setMaxWorldX(double maxWX) { - maxWorldX = maxWX; - } - - public double getMaxWorldX() { - return maxWorldX; - } - - /** - * Set minimum World coordinate value for the vertical Y dimension. The minimum value - * corresponds to the bottom of the component. - */ - public void setMinWorldY(double minWY) { - minWorldY = minWY; - } - - public double getMinWorldY() { - return minWorldY; - } - - /** - * Set maximum World coordinate value for the vertical Y dimension. The maximum value - * corresponds to the top of the component. - */ - public void setMaxWorldY(double maxWY) { - maxWorldY = maxWY; - } - - public double getMaxWorldY() { - return maxWorldY; - } - - /** Convert from graphics coordinates (pixels) to world coordinates. */ - public double convertGXtoWX(int gx) { - int width = getWidth(); - return minWorldX + ((maxWorldX - minWorldX) * gx) / width; - } - - public double convertGYtoWY(int gy) { - int height = getHeight(); - return minWorldY + ((maxWorldY - minWorldY) * (height - gy)) / height; - } - - /** Convert from world coordinates to graphics coordinates (pixels). */ - public int convertWXtoGX(double wx) { - int width = getWidth(); - return (int) (((wx - minWorldX) * width) / (maxWorldX - minWorldX)); - } - - public int convertWYtoGY(double wy) { - int height = getHeight(); - return height - (int) (((wy - minWorldY) * height) / (maxWorldY - minWorldY)); - } - - /** Clip wx to the min and max World X values. */ - public double clipWorldX(double wx) { - if (wx < minWorldX) - wx = minWorldX; - else if (wx > maxWorldX) - wx = maxWorldX; - return wx; - } - - /** Clip wy to the min and max World Y values. */ - public double clipWorldY(double wy) { - if (wy < minWorldY) - wy = minWorldY; - else if (wy > maxWorldY) - wy = maxWorldY; - return wy; - } - -} diff --git a/src/com/jsyn/unitgen/Add.java b/src/com/jsyn/unitgen/Add.java deleted file mode 100644 index 5a2a24c..0000000 --- a/src/com/jsyn/unitgen/Add.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * 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.unitgen; - -/** - * This unit performs a signed addition on its two inputs.
- * - *

- * output = inputA + inputB
- * 
- * - *
- * Note that signals connected to an InputPort are automatically added together so you may not need - * this unit. - * - * @author Phil Burk (C) 2009 Mobileer Inc - * @version 016 - * @see MultiplyAdd - * @see Subtract - */ -public class Add extends UnitBinaryOperator { - - @Override - public void generate(int start, int limit) { - double[] aValues = inputA.getValues(); - double[] bValues = inputB.getValues(); - double[] outputs = output.getValues(); - - // System.out.println("adder = " + this); - // System.out.println("A = " + aValues[0]); - for (int i = start; i < limit; i++) { - outputs[i] = aValues[i] + bValues[i]; - } - // System.out.println("add out = " + outputs[0]); - } -} diff --git a/src/com/jsyn/unitgen/AsymptoticRamp.java b/src/com/jsyn/unitgen/AsymptoticRamp.java deleted file mode 100644 index 8b51294..0000000 --- a/src/com/jsyn/unitgen/AsymptoticRamp.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * 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.unitgen; - -import com.jsyn.ports.UnitInputPort; -import com.jsyn.ports.UnitVariablePort; - -/** - * Output approaches Input exponentially. This unit provides a slowly changing value that approaches - * its Input value exponentially. The equation is: - * - *
- * Output = Output + Rate * (Input - Output);
- * 
- * - * Note that the output may never reach the value of the input. It approaches the input - * asymptotically. The Rate is calculated internally based on the value on the halfLife port. Rate - * is generally just slightly less than 1.0. - * - * @author Phil Burk (C) 2009 Mobileer Inc - * @version 016 - * @see LinearRamp - * @see ExponentialRamp - * @see ContinuousRamp - */ -public class AsymptoticRamp extends UnitFilter { - public UnitVariablePort current; - public UnitInputPort halfLife; - private double previousHalfLife = -1.0; - private double decayScalar = 0.99; - - /* Define Unit Ports used by connect() and set(). */ - public AsymptoticRamp() { - addPort(halfLife = new UnitInputPort(1, "HalfLife", 0.1)); - addPort(current = new UnitVariablePort("Current")); - } - - @Override - public void generate(int start, int limit) { - double[] outputs = output.getValues(); - double[] inputs = input.getValues(); - double currentHalfLife = halfLife.getValues()[0]; - double currentValue = current.getValue(); - double inputValue = currentValue; - - if (currentHalfLife != previousHalfLife) { - decayScalar = this.convertHalfLifeToMultiplier(currentHalfLife); - previousHalfLife = currentHalfLife; - } - - for (int i = start; i < limit; i++) { - inputValue = inputs[i]; - currentValue = currentValue + decayScalar * (inputValue - currentValue); - outputs[i] = currentValue; - } - - /* - * When current gets close to input, set current to input to prevent FP underflow, which can - * cause a severe performance degradation in 'C'. - */ - if (Math.abs(inputValue - currentValue) < VERY_SMALL_FLOAT) { - currentValue = inputValue; - } - - current.setValue(currentValue); - } -} diff --git a/src/com/jsyn/unitgen/BrownNoise.java b/src/com/jsyn/unitgen/BrownNoise.java deleted file mode 100644 index e70b7f4..0000000 --- a/src/com/jsyn/unitgen/BrownNoise.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 1997 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.unitgen; - -import com.jsyn.ports.UnitInputPort; -import com.jsyn.ports.UnitOutputPort; -import com.jsyn.util.PseudoRandom; - -/** - * BrownNoise unit. This unit uses a pseudo-random number generator to produce a noise related to - * Brownian Motion. A DC blocker is used to prevent runaway drift. - * - *
- * 
- * output = (previous * (1.0 - damping)) + (random * amplitude) 
- * 
- * 
- * - * The output drifts quite a bit and will generally exceed the range of +/1 amplitude. - * - * @author (C) 1997-2011 Phil Burk, Mobileer Inc - * @see WhiteNoise - * @see RedNoise - * @see PinkNoise - */ -public class BrownNoise extends UnitGenerator implements UnitSource { - private PseudoRandom randomNum; - /** Increasing the damping will effectively increase the cutoff - * frequency of a high pass filter that is used to block DC bias. - * Warning: setting this too close to zero can result in very large output values. - */ - public UnitInputPort damping; - public UnitInputPort amplitude; - public UnitOutputPort output; - private double previous; - - public BrownNoise() { - randomNum = new PseudoRandom(); - addPort(damping = new UnitInputPort("Damping")); - damping.setup(0.0001, 0.01, 0.1); - addPort(amplitude = new UnitInputPort("Amplitude", UnitOscillator.DEFAULT_AMPLITUDE)); - addPort(output = new UnitOutputPort("Output")); - } - - @Override - public void generate(int start, int limit) { - double[] amplitudes = amplitude.getValues(); - double[] outputs = output.getValues(); - double damper = 1.0 - damping.getValues()[0]; - - for (int i = start; i < limit; i++) { - double r = randomNum.nextRandomDouble() * amplitudes[i]; - outputs[i] = previous = (damper * previous) + r; - } - } - - @Override - public UnitOutputPort getOutput() { - return output; - } -} diff --git a/src/com/jsyn/unitgen/ChannelIn.java b/src/com/jsyn/unitgen/ChannelIn.java deleted file mode 100644 index c440b4f..0000000 --- a/src/com/jsyn/unitgen/ChannelIn.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * 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.unitgen; - -import com.jsyn.ports.UnitOutputPort; - -/** - * Provides access to one specific channel of the audio input. For ChannelIn to work you must call - * the {@link com.jsyn.Synthesizer} start() method with numInputChannels > 0. - * - * @author Phil Burk (C) 2009 Mobileer Inc - * @see ChannelOut - * @see LineIn - */ -public class ChannelIn extends UnitGenerator { - public UnitOutputPort output; - private int channelIndex; - - public ChannelIn() { - this(0); - } - - public ChannelIn(int channelIndex) { - addPort(output = new UnitOutputPort()); - setChannelIndex(channelIndex); - } - - public int getChannelIndex() { - return channelIndex; - } - - public void setChannelIndex(int channelIndex) { - this.channelIndex = channelIndex; - } - - @Override - public void generate(int start, int limit) { - double[] outputs = output.getValues(0); - double[] buffer = synthesisEngine.getInputBuffer(channelIndex); - for (int i = start; i < limit; i++) { - outputs[i] = buffer[i]; - } - } - -} diff --git a/src/com/jsyn/unitgen/ChannelOut.java b/src/com/jsyn/unitgen/ChannelOut.java deleted file mode 100644 index 8ef0677..0000000 --- a/src/com/jsyn/unitgen/ChannelOut.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * 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.unitgen; - -import com.jsyn.ports.UnitInputPort; - -/** - * Provides access to one channel of the audio output. - * For more than two channels you must call - * the {@link com.jsyn.Synthesizer} start() method with numOutputChannels > 2. - * - * @author Phil Burk (C) 2009 Mobileer Inc - * @see ChannelIn - */ -public class ChannelOut extends UnitGenerator { - public UnitInputPort input; - private int channelIndex; - - public ChannelOut() { - addPort(input = new UnitInputPort("Input")); - } - - public int getChannelIndex() { - return channelIndex; - } - - public void setChannelIndex(int channelIndex) { - this.channelIndex = channelIndex; - } - - /** - * This unit won't do anything unless you start() it. - */ - @Override - public boolean isStartRequired() { - return true; - } - - @Override - public void generate(int start, int limit) { - double[] inputs = input.getValues(0); - double[] buffer = synthesisEngine.getOutputBuffer(channelIndex); - for (int i = start; i < limit; i++) { - buffer[i] += inputs[i]; - } - } - -} diff --git a/src/com/jsyn/unitgen/Circuit.java b/src/com/jsyn/unitgen/Circuit.java deleted file mode 100644 index 01cb860..0000000 --- a/src/com/jsyn/unitgen/Circuit.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * 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.unitgen; - -import java.util.ArrayList; -import java.util.LinkedHashMap; - -import com.jsyn.engine.SynthesisEngine; -import com.jsyn.ports.UnitPort; - -/** - * Contains a list of units that are executed together. - * - * @author Phil Burk (C) 2009 Mobileer Inc - */ -public class Circuit extends UnitGenerator { - private ArrayList units = new ArrayList(); - - private final LinkedHashMap portAliases = new LinkedHashMap(); - - @Override - public void generate(int start, int limit) { - for (UnitGenerator unit : units) { - unit.generate(start, limit); - } - } - - /** - * Call flattenOutputs on subunits. Flatten output ports so we don't output a changing signal - * when stopped. - */ - @Override - public void flattenOutputs() { - for (UnitGenerator unit : units) { - unit.flattenOutputs(); - } - } - - /** - * Call setEnabled on subunits. - */ - @Override - public void setEnabled(boolean enabled) { - super.setEnabled(enabled); - for (UnitGenerator unit : units) { - unit.setEnabled(enabled); - } - } - - /** - * @deprecated ignored, frameRate comes from the SynthesisEngine - * @param frameRate - */ - @Deprecated - @Override - public void setFrameRate(int frameRate) { - super.setFrameRate(frameRate); - for (UnitGenerator unit : units) { - unit.setFrameRate(frameRate); - } - } - - @Override - public void setSynthesisEngine(SynthesisEngine engine) { - super.setSynthesisEngine(engine); - for (UnitGenerator unit : units) { - unit.setSynthesisEngine(engine); - } - } - - /** Add a unit to the circuit. */ - public void add(UnitGenerator unit) { - units.add(unit); - unit.setCircuit(this); - // Propagate circuit properties down into subunits. - unit.setEnabled(isEnabled()); - } - - public void usePreset(int presetIndex) { - } - - - /** - * Add an alternate name for looking up a port. - * @param port - * @param alias - */ - public void addPortAlias(UnitPort port, String alias) { - // Store in a hash table by an alternate name. - portAliases.put(alias.toLowerCase(), port); - } - - - /** - * Case-insensitive search for a port by its name or alias. - * @param portName - * @return matching port or null - */ - @Override - public UnitPort getPortByName(String portName) { - UnitPort port = super.getPortByName(portName); - if (port == null) { - port = portAliases.get(portName.toLowerCase()); - } - return port; - } - -} diff --git a/src/com/jsyn/unitgen/Compare.java b/src/com/jsyn/unitgen/Compare.java deleted file mode 100644 index 7de2e53..0000000 --- a/src/com/jsyn/unitgen/Compare.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * 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.unitgen; - -/** - * - Output 1.0 if inputA > inputB. Otherwise output 0.0. - * - * @author Phil Burk (C) 2009 Mobileer Inc - * @version 016 - * @see Maximum - */ -public class Compare extends UnitBinaryOperator { - @Override - public void generate(int start, int limit) { - double[] aValues = inputA.getValues(); - double[] bValues = inputB.getValues(); - double[] outputs = output.getValues(); - - for (int i = start; i < limit; i++) { - outputs[i] = (aValues[i] > bValues[i]) ? 1.0 : 0.0; - } - } -} diff --git a/src/com/jsyn/unitgen/ContinuousRamp.java b/src/com/jsyn/unitgen/ContinuousRamp.java deleted file mode 100644 index dd90445..0000000 --- a/src/com/jsyn/unitgen/ContinuousRamp.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * 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.unitgen; - -import com.jsyn.ports.UnitInputPort; -import com.jsyn.ports.UnitVariablePort; - -/** - * A ramp whose function over time is continuous in value and in slope. Also called an "S curve". - * - * @author Phil Burk (C) 2009 Mobileer Inc - * @version 016 - * @see LinearRamp - * @see ExponentialRamp - * @see AsymptoticRamp - */ -public class ContinuousRamp extends UnitFilter { - public UnitVariablePort current; - /** - * Time it takes to get from current value to input value when input is changed. Default value - * is 1.0 seconds. - */ - public UnitInputPort time; - private double previousInput = Double.MIN_VALUE; - // Coefficients for cubic polynomial. - private double a; - private double b; - private double d; - private int framesLeft; - - /* Define Unit Ports used by connect() and set(). */ - public ContinuousRamp() { - addPort(time = new UnitInputPort(1, "Time", 1.0)); - addPort(current = new UnitVariablePort("Current")); - } - - @Override - public void generate(int start, int limit) { - double[] outputs = output.getValues(); - double[] inputs = input.getValues(); - double currentTime = time.getValues()[0]; - double currentValue = current.getValue(); - double inputValue = currentValue; - - for (int i = start; i < limit; i++) { - inputValue = inputs[i]; - double x; - if (inputValue != previousInput) { - x = framesLeft; - // Calculate coefficients. - double currentSlope = x * ((3 * a * x) + (2 * b)); - - framesLeft = (int) (getSynthesisEngine().getFrameRate() * currentTime); - if (framesLeft < 1) { - framesLeft = 1; - } - x = framesLeft; - // Calculate coefficients. - d = inputValue; - double xsq = x * x; - b = ((3 * currentValue) - (currentSlope * x) - (3 * d)) / xsq; - a = (currentSlope - (2 * b * x)) / (3 * xsq); - previousInput = inputValue; - } - - if (framesLeft > 0) { - x = --framesLeft; - // Cubic polynomial. c==0 - currentValue = (x * (x * ((x * a) + b))) + d; - } - - outputs[i] = currentValue; - } - - current.setValue(currentValue); - } -} diff --git a/src/com/jsyn/unitgen/CrossFade.java b/src/com/jsyn/unitgen/CrossFade.java deleted file mode 100644 index 4375fa6..0000000 --- a/src/com/jsyn/unitgen/CrossFade.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * 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.unitgen; - -import com.jsyn.ports.UnitInputPort; -import com.jsyn.ports.UnitOutputPort; - -/** - * Linear CrossFade between parts of the input. - *

- * Mix input[0] and input[1] based on the value of "fade". When fade is -1, output is all input[0]. - * When fade is 0, output is half input[0] and half input[1]. When fade is +1, output is all - * input[1]. - *

- * - * @author Phil Burk (C) 2009 Mobileer Inc - * @version 016 - * @see Pan - */ -public class CrossFade extends UnitGenerator { - public UnitInputPort input; - public UnitInputPort fade; - public UnitOutputPort output; - - /* Define Unit Ports used by connect() and set(). */ - public CrossFade() { - addPort(input = new UnitInputPort(2, "Input")); - addPort(fade = new UnitInputPort("Fade")); - fade.setup(-1.0, 0.0, 1.0); - addPort(output = new UnitOutputPort()); - } - - @Override - public void generate(int start, int limit) { - double[] input0s = input.getValues(0); - double[] input1s = input.getValues(1); - double[] fades = fade.getValues(); - double[] outputs = output.getValues(); - for (int i = start; i < limit; i++) { - // Scale and offset to 0.0 to 1.0 range. - double gain = (fades[i] * 0.5) + 0.5; - outputs[i] = (input0s[i] * (1.0 - gain)) + (input1s[i] * gain); - } - } - -} diff --git a/src/com/jsyn/unitgen/Delay.java b/src/com/jsyn/unitgen/Delay.java deleted file mode 100644 index aa450a9..0000000 --- a/src/com/jsyn/unitgen/Delay.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * 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.unitgen; - -/** - * Simple non-interpolating delay. The delay line must be allocated by calling allocate(n). - * - * @author Phil Burk (C) 2009 Mobileer Inc - * @version 016 - * @see InterpolatingDelay - */ - -public class Delay extends UnitFilter { - private float[] buffer; - private int cursor; - private int numSamples; - - /** - * Allocate an internal array for the delay line. - * - * @param numSamples - */ - public void allocate(int numSamples) { - this.numSamples = numSamples; - buffer = new float[numSamples]; - } - - @Override - public void generate(int start, int limit) { - double[] inputs = input.getValues(); - double[] outputs = output.getValues(); - - for (int i = start; i < limit; i++) { - outputs[i] = buffer[cursor]; - buffer[cursor] = (float) inputs[i]; - cursor += 1; - if (cursor >= numSamples) { - cursor = 0; - } - } - } - -} diff --git a/src/com/jsyn/unitgen/Divide.java b/src/com/jsyn/unitgen/Divide.java deleted file mode 100644 index cddcd7c..0000000 --- a/src/com/jsyn/unitgen/Divide.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * 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.unitgen; - -/** - * This unit divides its two inputs.
- * - *

- * output = inputA / inputB
- * 
- * - *
- * Note that this unit is protected from dividing by zero. But you can still get some very big - * outputs. - * - * @author Phil Burk (C) 2009 Mobileer Inc - * @version 016 - * @see Multiply - * @see Subtract - */ -public class Divide extends UnitBinaryOperator { - - @Override - public void generate(int start, int limit) { - double[] aValues = inputA.getValues(); - double[] bValues = inputB.getValues(); - double[] outputs = output.getValues(); - for (int i = start; i < limit; i++) { - /* Prevent divide by zero crash. */ - double b = bValues[i]; - if (b == 0.0) { - b = 0.0000001; - } - - outputs[i] = aValues[i] / b; - } - } - -} diff --git a/src/com/jsyn/unitgen/DualInTwoOut.java b/src/com/jsyn/unitgen/DualInTwoOut.java deleted file mode 100644 index ec7dff5..0000000 --- a/src/com/jsyn/unitgen/DualInTwoOut.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * 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.unitgen; - -import com.jsyn.ports.UnitInputPort; -import com.jsyn.ports.UnitOutputPort; - -/** - * This unit splits a dual (stereo) input to two discrete outputs.
- * - *
- * outputA = input[0];
- * outputB = input[1];
- * 
- * - *
- * - * @author Phil Burk (C) 2009 Mobileer Inc - * @version 016 - */ - -public class DualInTwoOut extends UnitGenerator { - public UnitInputPort input; - public UnitOutputPort outputA; - public UnitOutputPort outputB; - - public DualInTwoOut() { - addPort(input = new UnitInputPort(2, "Input")); - addPort(outputA = new UnitOutputPort("OutputA")); - addPort(outputB = new UnitOutputPort("OutputB")); - } - - @Override - public void generate(int start, int limit) { - double[] input0s = input.getValues(0); - double[] input1s = input.getValues(1); - double[] outputAs = outputA.getValues(); - double[] outputBs = outputB.getValues(); - - for (int i = start; i < limit; i++) { - outputAs[i] = input0s[i]; - outputBs[i] = input1s[i]; - } - } -} diff --git a/src/com/jsyn/unitgen/EdgeDetector.java b/src/com/jsyn/unitgen/EdgeDetector.java deleted file mode 100644 index e314f7d..0000000 --- a/src/com/jsyn/unitgen/EdgeDetector.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 1997 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.unitgen; - -/** - * Output 1.0 if the input crosses from zero while rising. Otherwise output zero. The output is a - * single sample wide impulse. This can be used with a Latch to implement a "sample and hold" - * circuit. - * - * @author (C) 1997-2010 Phil Burk, Mobileer Inc - * @see Latch - */ -public class EdgeDetector extends UnitFilter { - private double previous = 0.0; - - public EdgeDetector() { - } - - @Override - public void generate(int start, int limit) { - double[] inputs = input.getValues(); - double[] outputs = output.getValues(); - - for (int i = start; i < limit; i++) { - double in = inputs[i]; - outputs[i] = ((previous <= 0.0) && (in > 0.0)) ? 1.0 : 0.0; - previous = in; - } - } -} diff --git a/src/com/jsyn/unitgen/EnvelopeAttackDecay.java b/src/com/jsyn/unitgen/EnvelopeAttackDecay.java deleted file mode 100644 index db3ecaa..0000000 --- a/src/com/jsyn/unitgen/EnvelopeAttackDecay.java +++ /dev/null @@ -1,145 +0,0 @@ -/* - * Copyright 2011 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.unitgen; - -import com.jsyn.engine.SynthesisEngine; -import com.jsyn.ports.UnitInputPort; - -/** - * Two stage Attack/Decay envelope that is triggered by an input level greater than THRESHOLD. This - * does not sustain. - * - * @author Phil Burk (C) 2011 Mobileer Inc - */ -public class EnvelopeAttackDecay extends UnitGate { - public static final double THRESHOLD = 0.01; - private static final double MIN_DURATION = (1.0 / 100000.0); - - /** - * Time in seconds for the rising stage of the envelope to go from 0.0 to 1.0. The attack is a - * linear ramp. - */ - public UnitInputPort attack; - /** - * Time in seconds for the falling stage to go from 0 dB to -90 dB. - */ - public UnitInputPort decay; - - public UnitInputPort amplitude; - - private enum State { - IDLE, ATTACKING, DECAYING - } - - private State state = State.IDLE; - private double scaler = 1.0; - private double level; - private double increment; - - public EnvelopeAttackDecay() { - super(); - addPort(attack = new UnitInputPort("Attack")); - attack.setup(0.001, 0.05, 8.0); - addPort(decay = new UnitInputPort("Decay")); - decay.setup(0.001, 0.2, 8.0); - addPort(amplitude = new UnitInputPort("Amplitude", 1.0)); - startIdle(); - } - - public void export(Circuit circuit, String prefix) { - circuit.addPort(attack, prefix + attack.getName()); - circuit.addPort(decay, prefix + decay.getName()); - } - - @Override - public void generate(int start, int limit) { - double[] amplitudes = amplitude.getValues(); - double[] outputs = output.getValues(); - - for (int i = start; i < limit;) { - boolean triggered = input.checkGate(i); - switch (state) { - case IDLE: - for (; i < limit; i++) { - outputs[i] = level; - if (triggered) { - startAttack(i); - break; - } - } - break; - case ATTACKING: - for (; i < limit; i++) { - // Increment first so we can render fast attacks. - level += increment; - if (level >= 1.0) { - level = 1.0; - outputs[i] = level * amplitudes[i]; - startDecay(i); - break; - } - outputs[i] = level * amplitudes[i]; - } - break; - case DECAYING: - for (; i < limit; i++) { - outputs[i] = level * amplitudes[i]; - level *= scaler; - if (triggered) { - startAttack(i); - break; - } else if (level < SynthesisEngine.DB90) { - input.checkAutoDisable(); - startIdle(); - break; - } - } - break; - } - } - } - - private void startIdle() { - state = State.IDLE; - level = 0.0; - } - - private void startAttack(int i) { - double[] attacks = attack.getValues(); - double duration = attacks[i]; - if (duration < MIN_DURATION) { - level = 1.0; - startDecay(i); - } else { - // assume going from 0.0 to 1.0 even if retriggering - increment = getFramePeriod() / duration; - state = State.ATTACKING; - } - } - - private void startDecay(int i) { - double[] decays = decay.getValues(); - double duration = decays[i]; - if (duration < MIN_DURATION) { - startIdle(); - } else { - scaler = getSynthesisEngine().convertTimeToExponentialScaler(duration); - state = State.DECAYING; - } - } - -} diff --git a/src/com/jsyn/unitgen/EnvelopeDAHDSR.java b/src/com/jsyn/unitgen/EnvelopeDAHDSR.java deleted file mode 100644 index c5ebe83..0000000 --- a/src/com/jsyn/unitgen/EnvelopeDAHDSR.java +++ /dev/null @@ -1,294 +0,0 @@ -/* - * Copyright 2010 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.unitgen; - -import com.jsyn.data.SegmentedEnvelope; -import com.jsyn.engine.SynthesisEngine; -import com.jsyn.ports.UnitInputPort; -import com.jsyn.ports.UnitOutputPort; - -/** - * Six stage envelope similar to an ADSR. DAHDSR is like an ADSR but with an additional Delay stage - * before the attack, and a Hold stage after the Attack. If Delay and Hold are both set to zero then - * it will act like an ADSR. The envelope is triggered when the input goes above THRESHOLD. The - * envelope is released when the input goes below THRESHOLD. The THRESHOLD is currently 0.01 but may - * change so it would be best to use an input signal that went from 0 to 1. Mathematically an - * exponential Release will never reach 0.0. But when it reaches -96 dB the DAHDSR just sets its - * output to 0.0 and stops. There is an example program in the ZIP archive called HearDAHDSR. It - * drives a DAHDSR with a square wave. - * - * @author Phil Burk (C) 2010 Mobileer Inc - * @see SegmentedEnvelope - */ -public class EnvelopeDAHDSR extends UnitGate implements UnitSource { - private static final double MIN_DURATION = (1.0 / 100000.0); - - /** - * Time in seconds for first stage of the envelope, before the attack. Typically zero. - */ - public UnitInputPort delay; - /** - * Time in seconds for the rising stage of the envelope to go from 0.0 to 1.0. The attack is a - * linear ramp. - */ - public UnitInputPort attack; - /** Time in seconds for the plateau between the attack and decay stages. */ - public UnitInputPort hold; - /** - * Time in seconds for the falling stage to go from 0 dB to -90 dB. The decay stage will stop at - * the sustain level. But we calculate the time to fall to -90 dB so that the decay - * rate will be unaffected by the sustain level. - */ - public UnitInputPort decay; - /** - * Level for the sustain stage. The envelope will hold here until the input goes to zero or - * less. This should be set between 0.0 and 1.0. - */ - public UnitInputPort sustain; - /** - * Time in seconds to go from 0 dB to -90 dB. This stage is triggered when the input goes to - * zero or less. The release stage will start from the sustain level. But we calculate the time - * to fall from full amplitude so that the release rate will be unaffected by the - * sustain level. - */ - public UnitInputPort release; - public UnitInputPort amplitude; - - enum State { - IDLE, DELAYING, ATTACKING, HOLDING, DECAYING, SUSTAINING, RELEASING - } - - private State state = State.IDLE; - private double countdown; - private double scaler = 1.0; - private double level; - private double increment; - - public EnvelopeDAHDSR() { - super(); - addPort(delay = new UnitInputPort("Delay", 0.0)); - delay.setup(0.0, 0.0, 2.0); - addPort(attack = new UnitInputPort("Attack", 0.1)); - attack.setup(0.01, 0.1, 8.0); - addPort(hold = new UnitInputPort("Hold", 0.0)); - hold.setup(0.0, 0.0, 2.0); - addPort(decay = new UnitInputPort("Decay", 0.2)); - decay.setup(0.01, 0.2, 8.0); - addPort(sustain = new UnitInputPort("Sustain", 0.5)); - sustain.setup(0.0, 0.5, 1.0); - addPort(release = new UnitInputPort("Release", 0.3)); - release.setup(0.01, 0.3, 8.0); - addPort(amplitude = new UnitInputPort("Amplitude", 1.0)); - } - - @Override - public void generate(int start, int limit) { - double[] sustains = sustain.getValues(); - double[] amplitudes = amplitude.getValues(); - double[] outputs = output.getValues(); - - for (int i = start; i < limit;) { - boolean triggered = input.checkGate(i); - switch (state) { - case IDLE: - for (; i < limit; i++) { - outputs[i] = level * amplitudes[i]; - if (triggered) { - startDelay(i); - break; - } - } - break; - - case DELAYING: - for (; i < limit; i++) { - outputs[i] = level * amplitudes[i]; - if (input.isOff()) { - startRelease(i); - break; - } else { - countdown -= 1; - if (countdown <= 0) { - startAttack(i); - break; - } - } - } - break; - - case ATTACKING: - for (; i < limit; i++) { - // Increment first so we can render fast attacks. - level += increment; - if (level >= 1.0) { - level = 1.0; - outputs[i] = level * amplitudes[i]; - startHold(i); - break; - } else { - outputs[i] = level * amplitudes[i]; - if (input.isOff()) { - startRelease(i); - break; - } - } - } - break; - - case HOLDING: - for (; i < limit; i++) { - outputs[i] = amplitudes[i]; // level is 1.0 - countdown -= 1; - if (countdown <= 0) { - startDecay(i); - break; - } else if (input.isOff()) { - startRelease(i); - break; - } - } - break; - - case DECAYING: - for (; i < limit; i++) { - outputs[i] = level * amplitudes[i]; - level *= scaler; // exponential decay - if (triggered) { - startDelay(i); - break; - } else if (level < sustains[i]) { - level = sustains[i]; - startSustain(i); - break; - } else if (level < SynthesisEngine.DB96) { - input.checkAutoDisable(); - startIdle(); - break; - } else if (input.isOff()) { - startRelease(i); - break; - } - } - break; - - case SUSTAINING: - for (; i < limit; i++) { - level = sustains[i]; - outputs[i] = level * amplitudes[i]; - if (triggered) { - startDelay(i); - break; - } else if (input.isOff()) { - startRelease(i); - break; - } - } - break; - - case RELEASING: - for (; i < limit; i++) { - outputs[i] = level * amplitudes[i]; - level *= scaler; // exponential decay - if (triggered) { - startDelay(i); - break; - } else if (level < SynthesisEngine.DB96) { - input.checkAutoDisable(); - startIdle(); - break; - } - } - break; - } - } - } - - private void startIdle() { - state = State.IDLE; - level = 0.0; - } - - private void startDelay(int i) { - double[] delays = delay.getValues(); - if (delays[i] <= 0.0) { - startAttack(i); - } else { - countdown = (int) (delays[i] * getFrameRate()); - state = State.DELAYING; - } - } - - private void startAttack(int i) { - double[] attacks = attack.getValues(); - double duration = attacks[i]; - if (duration < MIN_DURATION) { - level = 1.0; - startHold(i); - } else { - increment = getFramePeriod() / duration; - state = State.ATTACKING; - } - } - - private void startHold(int i) { - double[] holds = hold.getValues(); - if (holds[i] <= 0.0) { - startDecay(i); - } else { - countdown = (int) (holds[i] * getFrameRate()); - state = State.HOLDING; - } - } - - private void startDecay(int i) { - double[] decays = decay.getValues(); - double duration = decays[i]; - if (duration < MIN_DURATION) { - startSustain(i); - } else { - scaler = getSynthesisEngine().convertTimeToExponentialScaler(duration); - state = State.DECAYING; - } - } - - private void startSustain(int i) { - state = State.SUSTAINING; - } - - private void startRelease(int i) { - double[] releases = release.getValues(); - double duration = releases[i]; - if (duration < MIN_DURATION) { - duration = MIN_DURATION; - } - scaler = getSynthesisEngine().convertTimeToExponentialScaler(duration); - state = State.RELEASING; - } - - public void export(Circuit circuit, String prefix) { - circuit.addPort(attack, prefix + attack.getName()); - circuit.addPort(decay, prefix + decay.getName()); - circuit.addPort(sustain, prefix + sustain.getName()); - circuit.addPort(release, prefix + release.getName()); - } - - @Override - public UnitOutputPort getOutput() { - return output; - } - -} diff --git a/src/com/jsyn/unitgen/ExponentialRamp.java b/src/com/jsyn/unitgen/ExponentialRamp.java deleted file mode 100644 index 36159b4..0000000 --- a/src/com/jsyn/unitgen/ExponentialRamp.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright 2010 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.unitgen; - -import com.jsyn.ports.UnitInputPort; -import com.jsyn.ports.UnitVariablePort; - -/** - * Output approaches Input exponentially and will reach it in the specified time. - * - * @author Phil Burk (C) 2010 Mobileer Inc - * @version 016 - * @see LinearRamp - * @see AsymptoticRamp - * @see ContinuousRamp - */ -public class ExponentialRamp extends UnitFilter { - public UnitInputPort time; - public UnitVariablePort current; - - private double target; - private double timeHeld = 0.0; - private double scaler = 1.0; - - public ExponentialRamp() { - addPort(time = new UnitInputPort("Time")); - input.setup(0.0001, 1.0, 1.0); - addPort(current = new UnitVariablePort("Current", 1.0)); - } - - @Override - public void generate(int start, int limit) { - double[] outputs = output.getValues(); - double currentInput = input.getValues()[0]; - double currentTime = time.getValues()[0]; - double currentValue = current.getValue(); - - if (currentTime != timeHeld) { - scaler = convertTimeToExponentialScaler(currentTime, currentValue, currentInput); - timeHeld = currentTime; - } - - // If input has changed, start new segment. - // Equality check is OK because we set them exactly equal below. - if (currentInput != target) { - scaler = convertTimeToExponentialScaler(currentTime, currentValue, currentInput); - target = currentInput; - } - - if (currentValue < target) { - // Going up. - for (int i = start; i < limit; i++) { - currentValue = currentValue * scaler; - if (currentValue > target) { - currentValue = target; - scaler = 1.0; - } - outputs[i] = currentValue; - } - } else if (currentValue > target) { - // Going down. - for (int i = start; i < limit; i++) { - currentValue = currentValue * scaler; - if (currentValue < target) { - currentValue = target; - scaler = 1.0; - } - outputs[i] = currentValue; - } - - } else if (currentValue == target) { - for (int i = start; i < limit; i++) { - outputs[i] = target; - } - } - - current.setValue(currentValue); - } - - private double convertTimeToExponentialScaler(double duration, double source, double target) { - double product = source * target; - if (product <= 0.0000001) { - throw new IllegalArgumentException( - "Exponential ramp crosses zero or gets too close to zero."); - } - // Calculate scaler so that scaler^frames = target/source - double numFrames = duration * getFrameRate(); - return Math.pow((target / source), (1.0 / numFrames)); - } -} diff --git a/src/com/jsyn/unitgen/FFT.java b/src/com/jsyn/unitgen/FFT.java deleted file mode 100644 index 63fce50..0000000 --- a/src/com/jsyn/unitgen/FFT.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2013 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.unitgen; - -/** - * Periodically transform the complex input signal using an FFT to a complex spectral stream. This - * is probably not as useful as the SpectralFFT, which outputs complete spectra. - * - * @author Phil Burk (C) 2013 Mobileer Inc - * @see IFFT - * @see SpectralFFT - */ -public class FFT extends FFTBase { - public FFT() { - super(); - } - - @Override - protected int getSign() { - return 1; // 1 for FFT - } -} diff --git a/src/com/jsyn/unitgen/FFTBase.java b/src/com/jsyn/unitgen/FFTBase.java deleted file mode 100644 index 055c04b..0000000 --- a/src/com/jsyn/unitgen/FFTBase.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright 2013 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.unitgen; - -import com.jsyn.data.Spectrum; -import com.jsyn.ports.UnitInputPort; -import com.jsyn.ports.UnitOutputPort; -import com.softsynth.math.FourierMath; - -/** - * Periodically transform the complex input signal using an FFT to a complex spectral stream. - * - * @author Phil Burk (C) 2013 Mobileer Inc - * @version 016 - * @see IFFT - */ -public abstract class FFTBase extends UnitGenerator { - public UnitInputPort inputReal; - public UnitInputPort inputImaginary; - public UnitOutputPort outputReal; - public UnitOutputPort outputImaginary; - protected double[] realInput; - protected double[] realOutput; - protected double[] imaginaryInput; - protected double[] imaginaryOutput; - protected int cursor; - - protected FFTBase() { - addPort(inputReal = new UnitInputPort("InputReal")); - addPort(inputImaginary = new UnitInputPort("InputImaginary")); - addPort(outputReal = new UnitOutputPort("OutputReal")); - addPort(outputImaginary = new UnitOutputPort("OutputImaginary")); - setSize(Spectrum.DEFAULT_SIZE); - } - - public void setSize(int size) { - realInput = new double[size]; - realOutput = new double[size]; - imaginaryInput = new double[size]; - imaginaryOutput = new double[size]; - cursor = 0; - } - - public int getSize() { - return realInput.length; - } - - @Override - public void generate(int start, int limit) { - double[] inputRs = inputReal.getValues(); - double[] inputIs = inputImaginary.getValues(); - double[] outputRs = outputReal.getValues(); - double[] outputIs = outputImaginary.getValues(); - for (int i = start; i < limit; i++) { - realInput[cursor] = inputRs[i]; - imaginaryInput[cursor] = inputIs[i]; - outputRs[i] = realOutput[cursor]; - outputIs[i] = imaginaryOutput[cursor]; - cursor += 1; - // When it is full, do the FFT. - if (cursor == realInput.length) { - // Copy to output buffer so we can do the FFT in place. - System.arraycopy(realInput, 0, realOutput, 0, realInput.length); - System.arraycopy(imaginaryInput, 0, imaginaryOutput, 0, imaginaryInput.length); - FourierMath.transform(getSign(), realOutput.length, realOutput, imaginaryOutput); - cursor = 0; - } - } - } - - protected abstract int getSign(); -} diff --git a/src/com/jsyn/unitgen/FilterAllPass.java b/src/com/jsyn/unitgen/FilterAllPass.java deleted file mode 100644 index 749b2d6..0000000 --- a/src/com/jsyn/unitgen/FilterAllPass.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright 2014 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.unitgen; - -import com.jsyn.ports.UnitInputPort; - -/** - * AllPass filter using the following formula: - * - *
- * y(n) = -gain * x(n) + x(n - 1) + gain * y(n - 1)
- * 
- * - * where y(n) is Output, x(n) is Input, x(n-1) is a delayed copy of the input, and y(n-1) is a - * delayed copy of the output. An all-pass filter will pass all frequencies with equal amplitude. - * But it changes the phase relationships of the partials by delaying them by an amount proportional - * to their wavelength,. - * - * @author (C) 2014 Phil Burk, SoftSynth.com - * @see FilterLowPass - */ - -public class FilterAllPass extends UnitFilter { - /** Feedback gain. Should be less than 1.0. Default is 0.8. */ - public UnitInputPort gain; - - private double x1; - private double y1; - - public FilterAllPass() { - addPort(gain = new UnitInputPort("Gain", 0.8)); - } - - @Override - public void generate(int start, int limit) { - double[] inputs = input.getValues(); - double[] outputs = output.getValues(); - double g = gain.getValue(); - - for (int i = start; i < limit; i++) { - double x0 = inputs[i]; - y1 = (g * (y1 - x0)) + x1; - x1 = x0; - outputs[i] = y1; - } - - } -} diff --git a/src/com/jsyn/unitgen/FilterBandPass.java b/src/com/jsyn/unitgen/FilterBandPass.java deleted file mode 100644 index b103400..0000000 --- a/src/com/jsyn/unitgen/FilterBandPass.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * 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. - */ -/** - * Aug 21, 2009 - * com.jsyn.engine.units.Filter_HighPass.java - */ - -package com.jsyn.unitgen; - -/** - * Filter that allows frequencies around the center frequency to pass and blocks others. This filter - * is based on the BiQuad filter. Coefficients are updated whenever the frequency or Q changes. - * - * @author Phil Burk (C) 2009 Mobileer Inc Translated from 'C' to Java by Lisa - * Tolentino. - */ -public class FilterBandPass extends FilterBiquadCommon { - /** - * This method is by Filter_Biquad to update coefficients for the Filter_BandPass filter. - */ - @Override - public void updateCoefficients() { - double scalar = 1.0 / (1.0 + alpha); - - a0 = alpha * scalar; - a1 = 0.0; - a2 = -a0; - b1 = -2.0 * cos_omega * scalar; - b2 = (1.0 - alpha) * scalar; - } -} diff --git a/src/com/jsyn/unitgen/FilterBandStop.java b/src/com/jsyn/unitgen/FilterBandStop.java deleted file mode 100644 index d4f5249..0000000 --- a/src/com/jsyn/unitgen/FilterBandStop.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * 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.unitgen; - -/** - * Filter that blocks frequencies around the center frequency. This filter is based on the BiQuad - * filter. Coefficients are updated whenever the frequency or Q changes. - * - * @author Phil Burk (C) 2009 Mobileer Inc Translated from 'C' to Java by Lisa - * Tolentino. - */ -public class FilterBandStop extends FilterBiquadCommon { - - @Override - public void updateCoefficients() { - - // scalar = 1.0f / (1.0f + BQCM.alpha); - // A1_B1_Value = -2.0f * BQCM.cos_omega * scalar; - // - // csFilter->csFBQ_A0 = scalar; - // csFilter->csFBQ_A1 = A1_B1_Value; - // csFilter->csFBQ_A2 = scalar; - // csFilter->csFBQ_B1 = A1_B1_Value; - // csFilter->csFBQ_B2 = (1.0f - BQCM.alpha) * scalar; - - double scalar = 1.0 / (1.0 + alpha); - double a1_b1_value = -2.0 * cos_omega * scalar; - - this.a0 = scalar; - this.a1 = a1_b1_value; - this.a2 = scalar; - this.b1 = a1_b1_value; - this.b2 = (1.0 - alpha) * scalar; - } -} diff --git a/src/com/jsyn/unitgen/FilterBiquad.java b/src/com/jsyn/unitgen/FilterBiquad.java deleted file mode 100644 index f9b792f..0000000 --- a/src/com/jsyn/unitgen/FilterBiquad.java +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Copyright 2011 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.unitgen; - -import com.jsyn.ports.UnitInputPort; - -/** - * Base class for a set of IIR filters. - * - * @author Phil Burk (C) 2011 Mobileer Inc - * @see FilterBandStop - * @see FilterBandPass - * @see FilterLowPass - * @see FilterHighPass - * @see FilterTwoPolesTwoZeros - */ -public abstract class FilterBiquad extends TunableFilter { - public UnitInputPort amplitude; - - protected static final double MINIMUM_FREQUENCY = 0.00001; - protected static final double MINIMUM_GAIN = 0.00001; - protected static final double RATIO_MINIMUM = 0.499; - protected double a0; - protected double a1; - protected double a2; - protected double b1; - protected double b2; - private double x1; - private double x2; - private double y1; - private double y2; - protected double previousFrequency; - protected double omega; - protected double sin_omega; - protected double cos_omega; - - public FilterBiquad() { - addPort(amplitude = new UnitInputPort("Amplitude", 1.0)); - } - - /** - * Generic generate(int start, int limit) method calls this filter's recalculate() and - * performBiquadFilter(int, int) methods. - */ - @Override - public void generate(int start, int limit) { - recalculate(); - performBiquadFilter(start, limit); - } - - protected abstract void recalculate(); - - /** - * Each filter calls performBiquadFilter() through the generate(int, int) method. This method - * has converted Robert Bristow-Johnson's coefficients for the Direct I form in this way: Here - * is the equation that JSyn uses for this filter: - * - *
-     * y(n) = A0*x(n) + A1*x(n-1) + A2*x(n-2) -vB1*y(n-1) - B2*y(n-2)
-     * 
- * - * Here is the equation that Robert Bristow-Johnson uses: - * - *
-     * y[n] = (b0/a0)*x[n] + (b1/a0)*x[n-1] + (b2/a0)*x[n-2] - (a1/a0)*y[n-1] - (a2/a0)*y[n-2]
-     * 
- * - * So to translate between JSyn coefficients and RBJ coefficients: - * - *
-     * JSyn => RBJ
-     * A0 => b0/a0
-     * A1 => b1/a0
-     * A2 => b2/a0
-     * B1 => a1/a0
-     * B2 => a2/a0
-     * 
- * - * @param start - * @param limit - */ - public void performBiquadFilter(int start, int limit) { - double[] inputs = input.getValues(); - double[] amplitudes = amplitude.getValues(); - double[] outputs = output.getValues(); - double a0_jsyn, a1_jsyn, a2_jsyn, b1_jsyn, b2_jsyn; - double x0_jsyn, x1_jsyn, x2_jsyn, y1_jsyn, y2_jsyn; - - x1_jsyn = this.x1; - x2_jsyn = this.x2; - - y1_jsyn = this.y1; - y2_jsyn = this.y2; - - a0_jsyn = this.a0; - a1_jsyn = this.a1; - a2_jsyn = this.a2; - - b1_jsyn = this.b1; - b2_jsyn = this.b2; - - // Permute filter operations to reduce data movement. - for (int i = start; i < limit; i += 2) - - { - x0_jsyn = inputs[i]; - y2_jsyn = (a0_jsyn * x0_jsyn) + (a1_jsyn * x1_jsyn) + (a2_jsyn * x2_jsyn) - - (b1_jsyn * y1_jsyn) - (b2_jsyn * y2_jsyn); - - outputs[i] = amplitudes[i] * y2_jsyn; - - x2_jsyn = inputs[i + 1]; - y1_jsyn = (a0_jsyn * x2_jsyn) + (a1_jsyn * x0_jsyn) + (a2_jsyn * x1_jsyn) - - (b1_jsyn * y2_jsyn) - (b2_jsyn * y1_jsyn); - - outputs[i + 1] = amplitudes[i + 1] * y1_jsyn; - - x1_jsyn = x2_jsyn; - x2_jsyn = x0_jsyn; - } - - this.x1 = x1_jsyn; // save filter state for next time - this.x2 = x2_jsyn; - - // apply small bipolar impulse to prevent arithmetic underflow - this.y1 = y1_jsyn + VERY_SMALL_FLOAT; - this.y2 = y2_jsyn - VERY_SMALL_FLOAT; - } - - protected void calculateOmega(double ratio) { - if (ratio >= FilterBiquad.RATIO_MINIMUM) // keep a minimum - // distance from Nyquist - { - ratio = FilterBiquad.RATIO_MINIMUM; - } - - omega = 2.0 * Math.PI * ratio; - cos_omega = Math.cos(omega); // compute cosine - sin_omega = Math.sin(omega); // compute sine - } - -} diff --git a/src/com/jsyn/unitgen/FilterBiquadCommon.java b/src/com/jsyn/unitgen/FilterBiquadCommon.java deleted file mode 100644 index f21c8e7..0000000 --- a/src/com/jsyn/unitgen/FilterBiquadCommon.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * 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.unitgen; - -import com.jsyn.ports.UnitInputPort; - -/** - * Extend this class to create a filter that implements a Biquad filter with a Q port. - * - * @author Phil Burk (C) 2009 Mobileer Inc Translated from 'C' to Java by Lisa - * Tolentino. - */ -public abstract class FilterBiquadCommon extends FilterBiquad { - public UnitInputPort Q; - - protected final static double MINIMUM_Q = 0.00001; - private double previousQ; - protected double alpha; - - /** - * No-argument constructor instantiates the Biquad common and adds a Q port to this filter. - */ - public FilterBiquadCommon() { - addPort(Q = new UnitInputPort("Q")); - Q.setup(0.1, 1.0, 10.0); - } - - /** - * Calculate coefficients based on the filter type, eg. LowPass. - */ - public abstract void updateCoefficients(); - - public void computeBiquadCommon(double ratio, double Q) { - if (ratio >= FilterBiquad.RATIO_MINIMUM) // keep a minimum distance - // from Nyquist - { - ratio = FilterBiquad.RATIO_MINIMUM; - } - - omega = 2.0 * Math.PI * ratio; - cos_omega = Math.cos(omega); // compute cosine - sin_omega = Math.sin(omega); // compute sine - alpha = sin_omega / (2.0 * Q); // set alpha - // System.out.println("Q = " + Q + ", omega = " + omega + - // ", cos(omega) = " + cos_omega + ", alpha = " + alpha ); - } - - /** - * The recalculate() method checks and ensures that the frequency and Q values are at a minimum. - * It also only updates the Biquad coefficients if either frequency or Q have changed. - */ - @Override - public void recalculate() { - double frequencyValue = frequency.getValues()[0]; // grab frequency - // element (we'll - // only use - // element[0]) - double qValue = Q.getValues()[0]; // grab Q element (we'll only use - // element[0]) - - if (frequencyValue < MINIMUM_FREQUENCY) // ensure a minimum frequency - { - frequencyValue = MINIMUM_FREQUENCY; - } - - if (qValue < MINIMUM_Q) // ensure a minimum Q - { - qValue = MINIMUM_Q; - } - // only update changed values - if (isRecalculationNeeded(frequencyValue, qValue)) { - previousFrequency = frequencyValue; // hold previous frequency - previousQ = qValue; // hold previous Q - - double ratio = frequencyValue * getFramePeriod(); - computeBiquadCommon(ratio, qValue); - updateCoefficients(); - } - } - - protected boolean isRecalculationNeeded(double frequencyValue, double qValue) { - return (frequencyValue != previousFrequency) || (qValue != previousQ); - } - -} diff --git a/src/com/jsyn/unitgen/FilterBiquadShelf.java b/src/com/jsyn/unitgen/FilterBiquadShelf.java deleted file mode 100644 index 737d18d..0000000 --- a/src/com/jsyn/unitgen/FilterBiquadShelf.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * 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.unitgen; - -import com.jsyn.ports.UnitInputPort; - -/** - * This filter is based on the BiQuad filter and is used as a base class for FilterLowShelf and - * FilterHighShelf. Coefficients are updated whenever the frequency, gain or slope changes. - * - * @author Phil Burk (C) 2009 Mobileer Inc - */ -public abstract class FilterBiquadShelf extends FilterBiquad { - protected final static double MINIMUM_SLOPE = 0.00001; - - /** - * Gain of peak. Use 1.0 for flat response. - */ - public UnitInputPort gain; - - /** - * Shelf Slope parameter. When S = 1, the shelf slope is as steep as you can get it and remain - * monotonically increasing or decreasing gain with frequency. - */ - public UnitInputPort slope; - - private double prevGain; - private double prevSlope; - - private double beta; - protected double alpha; - protected double factorA; - protected double AP1; - protected double AM1; - protected double beta_sn; - protected double AP1cs; - protected double AM1cs; - - public FilterBiquadShelf() { - addPort(gain = new UnitInputPort("Gain", 1.0)); - addPort(slope = new UnitInputPort("Slope", 1.0)); - } - - /** - * Abstract method. Each filter must implement its update of coefficients. - */ - public abstract void updateCoefficients(); - - /** - * Compute coefficients for shelf filter if frequency, gain or slope have changed. - */ - @Override - public void recalculate() { - // Just look at first value to save time. - double frequencyValue = frequency.getValues()[0]; - if (frequencyValue < MINIMUM_FREQUENCY) { - frequencyValue = MINIMUM_FREQUENCY; - } - - double gainValue = gain.getValues()[0]; - if (gainValue < MINIMUM_GAIN) { - gainValue = MINIMUM_GAIN; - } - - double slopeValue = slope.getValues()[0]; - if (slopeValue < MINIMUM_SLOPE) { - slopeValue = MINIMUM_SLOPE; - } - - // Only do complex calculations if input changed. - if ((frequencyValue != previousFrequency) || (gainValue != prevGain) - || (slopeValue != prevSlope)) { - previousFrequency = frequencyValue; // hold previous frequency - prevGain = gainValue; - prevSlope = slopeValue; - - double ratio = frequencyValue * getFramePeriod(); - calculateOmega(ratio); - - factorA = Math.sqrt(gainValue); - - AP1 = factorA + 1.0; - AM1 = factorA - 1.0; - - /* Avoid sqrt(r<0) which hangs filter. */ - double beta2 = ((gainValue + 1.0) / slopeValue) - (AM1 * AM1); - beta = (beta2 < 0.0) ? 0.0 : Math.sqrt(beta2); - - beta_sn = beta * sin_omega; - AP1cs = AP1 * cos_omega; - AM1cs = AM1 * cos_omega; - - updateCoefficients(); - } - } - -} diff --git a/src/com/jsyn/unitgen/FilterFourPoles.java b/src/com/jsyn/unitgen/FilterFourPoles.java deleted file mode 100644 index 39a47c7..0000000 --- a/src/com/jsyn/unitgen/FilterFourPoles.java +++ /dev/null @@ -1,185 +0,0 @@ -/* - * Copyright 2014 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.unitgen; - -import com.jsyn.ports.UnitInputPort; - -/** - * Resonant filter in the style of the Moog ladder filter. This implementation is loosely based on: - * http://www.musicdsp.org/archive.php?classid=3#26 - * More interesting reading: - * http://dafx04.na.infn.it/WebProc/Proc/P_061.pdf - * http://www.acoustics.ed.ac.uk/wp-content/uploads/AMT_MSc_FinalProjects - * /2012__Daly__AMT_MSc_FinalProject_MoogVCF.pdf - * http://www.music.mcgill.ca/~ich/research/misc/papers/cr1071.pdf - * - * @author Phil Burk (C) 2014 Mobileer Inc - * @see FilterLowPass - */ -public class FilterFourPoles extends TunableFilter { - public UnitInputPort Q; - public UnitInputPort gain; - - private static final double MINIMUM_FREQUENCY = 1.0; // blows up if near 0.01 - private static final double MINIMUM_Q = 0.00001; - - //private static final double SATURATION_COEFFICIENT = 0.1666667; - private static final double SATURATION_COEFFICIENT = 0.2; - // Inflection point where slope is zero. - private static final double SATURATION_UPPER_INPUT = 1.0 / Math.sqrt(3.0 * SATURATION_COEFFICIENT); - private static final double SATURATION_LOWER_INPUT = 0.0 - SATURATION_UPPER_INPUT; - private static final double SATURATION_UPPER_OUTPUT = cubicPolynomial(SATURATION_UPPER_INPUT); - private static final double SATURATION_LOWER_OUTPUT = cubicPolynomial(SATURATION_LOWER_INPUT); - - private double x1; - private double x2; - private double x3; - private double x4; - private double y1; - private double y2; - private double y3; - private double y4; - - private double previousFrequency; - private double previousQ; - // filter coefficients - private double f; - private double fTo4th; - private double feedback; - - private boolean oversampled = true; - - public FilterFourPoles() { - addPort(Q = new UnitInputPort("Q")); - frequency.setup(40.0, DEFAULT_FREQUENCY, 4000.0); - Q.setup(0.1, 2.0, 10.0); - } - - /** - * The recalculate() method checks and ensures that the frequency and Q values are at a minimum. - * It also only updates the coefficients if either frequency or Q have changed. - */ - public void recalculate() { - double frequencyValue = frequency.getValues()[0]; - double qValue = Q.getValues()[0]; - - if (frequencyValue < MINIMUM_FREQUENCY) // ensure a minimum frequency - { - frequencyValue = MINIMUM_FREQUENCY; - } - if (qValue < MINIMUM_Q) // ensure a minimum Q - { - qValue = MINIMUM_Q; - } - - // Only recompute coefficients if changed. - if ((frequencyValue != previousFrequency) || (qValue != previousQ)) { - previousFrequency = frequencyValue; - previousQ = qValue; - computeCoefficients(); - } - } - - private void computeCoefficients() { - double normalizedFrequency = previousFrequency * getFramePeriod(); - double fudge = 4.9 - 0.27 * previousQ; - if (fudge < 3.0) - fudge = 3.0; - f = normalizedFrequency * (oversampled ? 1.0 : 2.0) * fudge; - - double fSquared = f * f; - fTo4th = fSquared * fSquared; - feedback = 0.5 * previousQ * (1.0 - 0.15 * fSquared); - } - - @Override - public void generate(int start, int limit) { - double[] inputs = input.getValues(); - double[] outputs = output.getValues(); - - recalculate(); - - for (int i = start; i < limit; i++) { - double x0 = inputs[i]; - - if (oversampled) { - oneSample(0.0); - } - oneSample(x0); - outputs[i] = y4; - } - - // apply small bipolar impulse to prevent arithmetic underflow - y1 += VERY_SMALL_FLOAT; - y2 -= VERY_SMALL_FLOAT; - } - - private void oneSample(double x0) { - final double coeff = 0.3; - x0 -= y4 * feedback; // feedback - x0 *= 0.35013 * fTo4th; - y1 = x0 + coeff * x1 + (1 - f) * y1; // pole 1 - x1 = x0; - y2 = y1 + coeff * x2 + (1 - f) * y2; // pole 2 - x2 = y1; - y3 = y2 + coeff * x3 + (1 - f) * y3; // pole 3 - x3 = y2; - y4 = y3 + coeff * x4 + (1 - f) * y4; // pole 4 - y4 = clip(y4); - x4 = y3; - } - - public boolean isOversampled() { - return oversampled; - } - - public void setOversampled(boolean oversampled) { - this.oversampled = oversampled; - } - - // Soft saturation. This used to blow up the filter! - private static double cubicPolynomial(double x) { - return x - (x * x * x * SATURATION_COEFFICIENT); - } - - private static double clip(double x) { - if (x > SATURATION_UPPER_INPUT) { - return SATURATION_UPPER_OUTPUT; - } else if (x < SATURATION_LOWER_INPUT) { - return SATURATION_LOWER_OUTPUT; - } else { - return cubicPolynomial(x); - } - } - - public void reset() { - x1 = 0.0; - x2 = 0.0; - x3 = 0.0; - x4 = 0.0; - y1 = 0.0; - y2 = 0.0; - y3 = 0.0; - y4 = 0.0; - - previousFrequency = 0.0; - previousQ = 0.0; - f = 0.0; - fTo4th = 0.0; - feedback = 0.0; - } -} diff --git a/src/com/jsyn/unitgen/FilterHighPass.java b/src/com/jsyn/unitgen/FilterHighPass.java deleted file mode 100644 index 76ad6b9..0000000 --- a/src/com/jsyn/unitgen/FilterHighPass.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * 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. - */ -/** - * Aug 21, 2009 - * com.jsyn.engine.units.Filter_HighPass.java - */ - -package com.jsyn.unitgen; - -/** - * Filter that allows frequencies above the center frequency to pass. This filter is based on the - * BiQuad filter. Coefficients are updated whenever the frequency or Q changes. - * - * @author Phil Burk (C) 2009 Mobileer Inc Translated from 'C' to Java by Lisa - * Tolentino. - */ -public class FilterHighPass extends FilterBiquadCommon { - /** - * This method is used by Filter_Biquad to update coefficients for the Filter_HighPass filter. - */ - @Override - public void updateCoefficients() { - double scalar = 1.0 / (1.0 + alpha); - double onePlusCosine = 1.0 + cos_omega; - double a0_a2_value = onePlusCosine * 0.5 * scalar; - - this.a0 = a0_a2_value; - this.a1 = -onePlusCosine * scalar; - this.a2 = a0_a2_value; - this.b1 = -2.0 * cos_omega * scalar; - this.b2 = (1.0 - alpha) * scalar; - } -} diff --git a/src/com/jsyn/unitgen/FilterHighShelf.java b/src/com/jsyn/unitgen/FilterHighShelf.java deleted file mode 100644 index 449090a..0000000 --- a/src/com/jsyn/unitgen/FilterHighShelf.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * 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.unitgen; - -/** - * HighShelf Filter. This creates a flat response above the cutoff frequency. This filter is - * sometimes used at the end of a bank of EQ filters. - * - * @author Phil Burk (C) 2009 Mobileer Inc - */ -public class FilterHighShelf extends FilterBiquadShelf { - /** - * This method is called by by Filter_BiquadShelf to update coefficients. - */ - @Override - public void updateCoefficients() { - double scalar = 1.0 / (AP1 - AM1cs + beta_sn); - a0 = factorA * (AP1 + AM1cs + beta_sn) * scalar; - a1 = -2.0 * factorA * (AM1 + AP1cs) * scalar; - a2 = factorA * (AP1 + AM1cs - beta_sn) * scalar; - b1 = 2.0 * (AM1 - AP1cs) * scalar; - b2 = (AP1 - AM1cs - beta_sn) * scalar; - } -} diff --git a/src/com/jsyn/unitgen/FilterLowPass.java b/src/com/jsyn/unitgen/FilterLowPass.java deleted file mode 100644 index 1557367..0000000 --- a/src/com/jsyn/unitgen/FilterLowPass.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * 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. - */ -/** - * Aug 21, 2009 - * com.jsyn.engine.units.Filter_HighPass.java - */ - -package com.jsyn.unitgen; - -/** - * Filter that allows frequencies below the center frequency to pass. This filter is based on the - * BiQuad filter. Coefficients are updated whenever the frequency or Q changes. - * - * @author Phil Burk (C) 2009 Mobileer Inc Translated from 'C' to Java by Lisa - * Tolentino. - * - * @see FilterFourPoles - */ -public class FilterLowPass extends FilterBiquadCommon { - - /** - * This method is by FilterBiquad to update coefficients for the lowpass filter. - */ - @Override - public void updateCoefficients() { - - // scalar = 1.0f / (1.0f + BQCM.alpha); - // omc = (1.0f - BQCM.cos_omega); - // A0_A2_Value = omc * 0.5f * scalar; - // // translating from RBJ coefficients - // // A0 = (b0/(2*a0) - // // = ((1 - cos_omega)/2) / (1 + alpha) - // // = (omc*0.5) / (1 + alpha) - // // = (omc*0.5) * (1.0/(1 + alpha)) - // // = omc * 0.5 * scalar - // csFilter->csFBQ_A0 = A0_A2_Value; - // csFilter->csFBQ_A1 = omc * scalar; - // csFilter->csFBQ_A2 = A0_A2_Value; - // csFilter->csFBQ_B1 = -2.0f * BQCM.cos_omega * scalar; - // csFilter->csFBQ_B2 = (1.0f - BQCM.alpha) * scalar; - - double scalar = 1.0 / (1.0 + alpha); - double oneMinusCosine = 1.0 - cos_omega; - double a0_a2_value = oneMinusCosine * 0.5 * scalar; - - this.a0 = a0_a2_value; - this.a1 = oneMinusCosine * scalar; - this.a2 = a0_a2_value; - this.b1 = -2.0 * cos_omega * scalar; - this.b2 = (1.0 - alpha) * scalar; - } -} diff --git a/src/com/jsyn/unitgen/FilterLowShelf.java b/src/com/jsyn/unitgen/FilterLowShelf.java deleted file mode 100644 index cf41f45..0000000 --- a/src/com/jsyn/unitgen/FilterLowShelf.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * 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.unitgen; - -/** - * LowShelf Filter. This creates a flat response below the cutoff frequency. This filter is - * sometimes used at the end of a bank of EQ filters. This filter is based on the BiQuad filter. - * Coefficients are updated whenever the frequency or Q changes. - * - * @author Phil Burk (C) 2009 Mobileer Inc - */ -public class FilterLowShelf extends FilterBiquadShelf { - - /** - * This method is called by Filter_BiquadShelf to update coefficients. - */ - @Override - public void updateCoefficients() { - double scalar = 1.0 / (AP1 + AM1cs + beta_sn); - a0 = factorA * (AP1 - AM1cs + beta_sn) * scalar; - a1 = 2.0 * factorA * (AM1 - AP1cs) * scalar; - a2 = factorA * (AP1 - AM1cs - beta_sn) * scalar; - b1 = -2.0 * (AM1 + AP1cs) * scalar; - b2 = (AP1 + AM1cs - beta_sn) * scalar; - } -} diff --git a/src/com/jsyn/unitgen/FilterOnePole.java b/src/com/jsyn/unitgen/FilterOnePole.java deleted file mode 100644 index 090e42b..0000000 --- a/src/com/jsyn/unitgen/FilterOnePole.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * 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.unitgen; - -import com.jsyn.ports.UnitVariablePort; - -/** - * First Order, One Pole filter using the following formula: - * - *
- * y(n) = A0 * x(n) - B1 * y(n - 1)
- * 
- * - * where y(n) is Output, x(n) is Input and y(n-1) is a delayed copy of the output. This filter is a - * recursive IIR or Infinite Impulse Response filter. It can be unstable depending on the values of - * the coefficients. This can be useful as a low-pass filter, or a "leaky integrator". A thorough - * description of the digital filter theory needed to fully describe this filter is beyond the scope - * of this document. Calculating coefficients is non-intuitive; the interested user is referred to - * one of the standard texts on filter theory (e.g., Moore, "Elements of Computer Music", section - * 2.4). - * - * @author Phil Burk (C) 2009 Mobileer Inc - * @see FilterLowPass - */ -public class FilterOnePole extends UnitFilter { - public UnitVariablePort a0; - public UnitVariablePort b1; - private double y1; - - public FilterOnePole() { - addPort(a0 = new UnitVariablePort("A0", 0.6)); - addPort(b1 = new UnitVariablePort("B1", -0.3)); - } - - @Override - public void generate(int start, int limit) { - double[] inputs = input.getValues(); - double[] outputs = output.getValues(); - double a0v = a0.getValue(); - double b1v = b1.getValue(); - - for (int i = start; i < limit; i++) { - double x0 = inputs[i]; - outputs[i] = y1 = (a0v * x0) - (b1v * y1); - } - - } -} diff --git a/src/com/jsyn/unitgen/FilterOnePoleOneZero.java b/src/com/jsyn/unitgen/FilterOnePoleOneZero.java deleted file mode 100644 index ed1868c..0000000 --- a/src/com/jsyn/unitgen/FilterOnePoleOneZero.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright 1997 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.unitgen; - -import com.jsyn.ports.UnitVariablePort; - -/** - * First Order, One Pole, One Zero filter using the following formula: - * - *
- * y(n) = A0 * x(n) + A1 * x(n - 1) - B1 * y(n - 1)
- * 
- * - * where y(n) is Output, x(n) is Input, x(n-1) is a delayed copy of the input, and y(n-1) is a - * delayed copy of the output. This filter is a recursive IIR or Infinite Impulse Response filter. - * it can be unstable depending on the values of the coefficients. A thorough description of the - * digital filter theory needed to fully describe this filter is beyond the scope of this document. - * Calculating coefficients is non-intuitive; the interested user is referred to one of the standard - * texts on filter theory (e.g., Moore, "Elements of Computer Music", section 2.4). - * - * @author (C) 1997-2009 Phil Burk, SoftSynth.com - * @see FilterLowPass - */ - -public class FilterOnePoleOneZero extends UnitFilter { - public UnitVariablePort a0; - public UnitVariablePort a1; - public UnitVariablePort b1; - - private double x1; - private double y1; - - public FilterOnePoleOneZero() { - addPort(a0 = new UnitVariablePort("A0")); - addPort(a1 = new UnitVariablePort("A1")); - addPort(b1 = new UnitVariablePort("B1")); - } - - @Override - public void generate(int start, int limit) { - double[] inputs = input.getValues(); - double[] outputs = output.getValues(); - double a0v = a0.getValue(); - double a1v = a1.getValue(); - double b1v = b1.getValue(); - - for (int i = start; i < limit; i++) { - double x0 = inputs[i]; - outputs[i] = y1 = (a0v * x0) + (a1v * x1) + (b1v * y1); - x1 = x0; - } - - } -} diff --git a/src/com/jsyn/unitgen/FilterOneZero.java b/src/com/jsyn/unitgen/FilterOneZero.java deleted file mode 100644 index 2a07a16..0000000 --- a/src/com/jsyn/unitgen/FilterOneZero.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2011 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.unitgen; - -import com.jsyn.ports.UnitVariablePort; - -/** - * First Order, One Zero filter using the following formula: - * - *
- * y(n) = A0 * x(n) + A1 * x(n - 1)
- * 
- * - * where y(n) is Output, x(n) is Input and x(n-1) is Input at the prior sample tick. Setting A1 - * positive gives a low-pass response; setting A1 negative gives a high-pass response. The bandwidth - * of this filter is fairly high, so it often serves a building block by being cascaded with other - * filters. If A0 and A1 are both 0.5, then this filter is a simple averaging lowpass filter, with a - * zero at SR/2 = 22050 Hz. If A0 is 0.5 and A1 is -0.5, then this filter is a high pass filter, - * with a zero at 0.0 Hz. A thorough description of the digital filter theory needed to fully - * describe this filter is beyond the scope of this document. Calculating coefficients is - * non-intuitive; the interested user is referred to one of the standard texts on filter theory - * (e.g., Moore, "Elements of Computer Music", section 2.4). - * - * @author Phil Burk (C) 2011 Mobileer Inc - * @see FilterLowPass - */ -public class FilterOneZero extends UnitFilter { - public UnitVariablePort a0; - public UnitVariablePort a1; - private double x1; - - public FilterOneZero() { - addPort(a0 = new UnitVariablePort("A0", 0.5)); - addPort(a1 = new UnitVariablePort("A1", 0.5)); - } - - @Override - public void generate(int start, int limit) { - double[] inputs = input.getValues(); - double[] outputs = output.getValues(); - double a0v = a0.getValue(); - double a1v = a1.getValue(); - - for (int i = start; i < limit; i++) { - double x0 = inputs[i]; - outputs[i] = (a0v * x0) + (a1v * x1); - x1 = x0; - } - - } -} diff --git a/src/com/jsyn/unitgen/FilterPeakingEQ.java b/src/com/jsyn/unitgen/FilterPeakingEQ.java deleted file mode 100644 index bec7096..0000000 --- a/src/com/jsyn/unitgen/FilterPeakingEQ.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * 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.unitgen; - -import com.jsyn.ports.UnitInputPort; - -/** - * PeakingEQ Filter. This can be used to raise or lower the gain around the cutoff frequency. This - * filter is sometimes used in the middle of a bank of EQ filters. This filter is based on the - * BiQuad filter. Coefficients are updated whenever the frequency or Q changes. - * - * @author Phil Burk (C) 2009 Mobileer Inc - */ -public class FilterPeakingEQ extends FilterBiquadCommon { - public UnitInputPort gain; - - private double previousGain; - - public FilterPeakingEQ() { - addPort(gain = new UnitInputPort("Gain", 1.0)); - } - - @Override - protected boolean isRecalculationNeeded(double frequencyValue, double qValue) { - double currentGain = gain.getValues()[0]; - if (currentGain < MINIMUM_GAIN) { - currentGain = MINIMUM_GAIN; - } - - boolean needed = super.isRecalculationNeeded(frequencyValue, qValue); - needed |= (previousGain != currentGain); - - previousGain = currentGain; - return needed; - } - - @Override - public void updateCoefficients() { - double factorA = Math.sqrt(previousGain); - double alphaTimesA = alpha * factorA; - double alphaOverA = alpha / factorA; - // Note this is not the normal scalar! - double scalar = 1.0 / (1.0 + alphaOverA); - double a1_b1_value = -2.0 * cos_omega * scalar; - - this.a0 = (1.0 + alphaTimesA) * scalar; - - this.a1 = a1_b1_value; - this.a2 = (1.0 - alphaTimesA) * scalar; - - this.b1 = a1_b1_value; - this.b2 = (1.0 - alphaOverA) * scalar; - } -} diff --git a/src/com/jsyn/unitgen/FilterStateVariable.java b/src/com/jsyn/unitgen/FilterStateVariable.java deleted file mode 100644 index 74dde5e..0000000 --- a/src/com/jsyn/unitgen/FilterStateVariable.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * 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.unitgen; - -import com.jsyn.ports.UnitInputPort; -import com.jsyn.ports.UnitOutputPort; - -/** - * A versatile filter described in Hal Chamberlain's "Musical Applications of MicroProcessors". It - * is convenient because its frequency and resonance can each be controlled by a single value. The - * "output" port of this filter is the "lowPass" output multiplied by the "amplitude" - * - * @author Phil Burk (C) 2009 Mobileer Inc - * @see FilterLowPass - * @see FilterHighPass - * @see FilterFourPoles - */ -public class FilterStateVariable extends TunableFilter { - /** - * Amplitude of Output in the range of 0.0 to 1.0. SIGNAL_TYPE_RAW_SIGNED Defaults to 1.0 - *

- * Note that the amplitude only affects the "output" port and not the lowPass, bandPass or - * highPass signals. Use a Multiply unit if you need to scale those signals. - */ - public UnitInputPort amplitude; - - /** - * Controls feedback that causes self oscillation. Actually 1/Q - SIGNAL_TYPE_RAW_SIGNED in the - * range of 0.0 to 1.0. Defaults to 0.125. - */ - public UnitInputPort resonance; - /** - * Low pass filtered signal. - *

- * Note that this signal is not affected by the amplitude port. - */ - public UnitOutputPort lowPass; - /** - * Band pass filtered signal. - *

- * Note that this signal is not affected by the amplitude port. - */ - public UnitOutputPort bandPass; - /** - * High pass filtered signal. - *

- * Note that this signal is not affected by the amplitude port. - */ - public UnitOutputPort highPass; - - private double freqInternal; - private double previousFrequency = Double.MAX_VALUE; // So we trigger an immediate update. - private double lowPassValue; - private double bandPassValue; - - /** - * No-argument constructor instantiates the Biquad common and adds an amplitude port to this - * filter. - */ - public FilterStateVariable() { - frequency.set(440.0); - addPort(resonance = new UnitInputPort("Resonance", 0.2)); - addPort(amplitude = new UnitInputPort("Amplitude", 1.0)); - addPort(lowPass = new UnitOutputPort("LowPass")); - addPort(bandPass = new UnitOutputPort("BandPass")); - addPort(highPass = new UnitOutputPort("HighPass")); - } - - @Override - public void generate(int start, int limit) { - double[] inputs = input.getValues(); - double[] outputs = output.getValues(); - double[] frequencies = frequency.getValues(); - double[] amplitudes = amplitude.getValues(); - double[] reses = resonance.getValues(); - double[] lows = lowPass.getValues(); - double[] highs = highPass.getValues(); - double[] bands = bandPass.getValues(); - - double newFreq = frequencies[0]; - if (newFreq != previousFrequency) { - previousFrequency = newFreq; - freqInternal = 2.0 * Math.sin(Math.PI * newFreq * getFramePeriod()); - } - - for (int i = start; i < limit; i++) { - lowPassValue = (freqInternal * bandPassValue) + lowPassValue; - // Clip between -1 and +1 to prevent blowup. - lowPassValue = (lowPassValue < -1.0) ? -1.0 : ((lowPassValue > 1.0) ? 1.0 - : lowPassValue); - lows[i] = lowPassValue; - - outputs[i] = lowPassValue * (amplitudes[i]); - double highPassValue = inputs[i] - (reses[i] * bandPassValue) - lowPassValue; - // System.out.println("low = " + lowPassValue + ", band = " + bandPassValue + - // ", high = " + highPassValue ); - highs[i] = highPassValue; - - bandPassValue = (freqInternal * highPassValue) + bandPassValue; - bands[i] = bandPassValue; - // System.out.println("low = " + lowPassValue + ", band = " + bandPassValue + - // ", high = " + highPassValue ); - } - } - -} diff --git a/src/com/jsyn/unitgen/FilterTwoPoles.java b/src/com/jsyn/unitgen/FilterTwoPoles.java deleted file mode 100644 index 0c68a64..0000000 --- a/src/com/jsyn/unitgen/FilterTwoPoles.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright 1997 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.unitgen; - -import com.jsyn.ports.UnitVariablePort; - -/** - * Second Order, Two Pole filter using the following formula: - * - *

- * y(n) = A0 * x(n) - B1 * y(n - 1) - B2 * y(n - 2)
- * 
- * - * where y(n) is Output, x(n) is Input, and y(n-1) is a delayed copy of the output. This filter is a - * recursive IIR or Infinite Impulse Response filter. it can be unstable depending on the values of - * the coefficients. A thorough description of the digital filter theory needed to fully describe - * this filter is beyond the scope of this document. Calculating coefficients is non-intuitive; the - * interested user is referred to one of the standard texts on filter theory (e.g., Moore, - * "Elements of Computer Music", section 2.4). - * - * @author (C) 1997-2009 Phil Burk, Mobileer Inc - */ - -public class FilterTwoPoles extends UnitFilter { - public UnitVariablePort a0; - public UnitVariablePort b1; - public UnitVariablePort b2; - private double y1; - private double y2; - - public FilterTwoPoles() { - addPort(a0 = new UnitVariablePort("A0")); - addPort(b1 = new UnitVariablePort("B1")); - addPort(b2 = new UnitVariablePort("B2")); - } - - @Override - public void generate(int start, int limit) { - double[] inputs = input.getValues(); - double[] outputs = output.getValues(); - double a0v = a0.getValue(); - double b1v = b1.getValue(); - double b2v = b2.getValue(); - - for (int i = start; i < limit; i++) { - double x0 = inputs[i]; - outputs[i] = y1 = 2.0 * ((a0v * x0) + (b1v * y1) + (b2v * y2)); - y2 = y1; - } - - } -} diff --git a/src/com/jsyn/unitgen/FilterTwoPolesTwoZeros.java b/src/com/jsyn/unitgen/FilterTwoPolesTwoZeros.java deleted file mode 100644 index cde279f..0000000 --- a/src/com/jsyn/unitgen/FilterTwoPolesTwoZeros.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright 1997 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.unitgen; - -import com.jsyn.ports.UnitVariablePort; - -/** - * Second Order, Two Pole, Two Zero filter using the following formula: - * - *
- * y(n) = 2.0 * (A0 * x(n) + A1 * x(n - 1) + A2 * x(n - 2) - B1 * y(n - 1) - B2 * y(n - 2))
- * 
- * - * where y(n) is Output, x(n) is Input, x(n-1) is a delayed copy of the input, and y(n-1) is a - * delayed copy of the output. This filter is a recursive IIR or Infinite Impulse Response filter. - * It can be unstable depending on the values of the coefficients. This filter is basically the same - * as the FilterBiquad with different ports. A thorough description of the digital filter theory - * needed to fully describe this filter is beyond the scope of this document. Calculating - * coefficients is non-intuitive; the interested user is referred to one of the standard texts on - * filter theory (e.g., Moore, "Elements of Computer Music", section 2.4). Special thanks to Robert - * Bristow-Johnson for contributing his filter equations to the music-dsp list. They were used for - * calculating the coefficients for the lowPass, highPass, and other parametric filter calculations. - * - * @author (C) 1997-2009 Phil Burk, SoftSynth.com - */ - -public class FilterTwoPolesTwoZeros extends UnitFilter { - public UnitVariablePort a0; - public UnitVariablePort a1; - public UnitVariablePort a2; - public UnitVariablePort b1; - public UnitVariablePort b2; - private double x1; - private double y1; - private double x2; - private double y2; - - public FilterTwoPolesTwoZeros() { - addPort(a0 = new UnitVariablePort("A0")); - addPort(a1 = new UnitVariablePort("A1")); - addPort(a2 = new UnitVariablePort("A2")); - addPort(b1 = new UnitVariablePort("B1")); - addPort(b2 = new UnitVariablePort("B2")); - } - - @Override - public void generate(int start, int limit) { - double[] inputs = input.getValues(); - double[] outputs = output.getValues(); - double a0v = a0.getValue(); - double a1v = a1.getValue(); - double a2v = a2.getValue(); - double b1v = b1.getValue(); - double b2v = b2.getValue(); - - for (int i = start; i < limit; i++) { - double x0 = inputs[i]; - outputs[i] = y1 = 2.0 * ((a0v * x0) + (a1v * x1) + (a2v * x2) + (b1v * y1) + (b2v * y2)); - x2 = x1; - x1 = x0; - y2 = y1; - } - - } -} diff --git a/src/com/jsyn/unitgen/FixedRateMonoReader.java b/src/com/jsyn/unitgen/FixedRateMonoReader.java deleted file mode 100644 index c6edc23..0000000 --- a/src/com/jsyn/unitgen/FixedRateMonoReader.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * 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.unitgen; - -import com.jsyn.ports.UnitOutputPort; - -/** - * Simple sample player. Play one sample per audio frame with no interpolation. - * - * @author Phil Burk (C) 2009 Mobileer Inc - */ -public class FixedRateMonoReader extends SequentialDataReader { - - public FixedRateMonoReader() { - addPort(output = new UnitOutputPort()); - } - - @Override - public void generate(int start, int limit) { - - double[] amplitudes = amplitude.getValues(); - double[] outputs = output.getValues(); - - for (int i = start; i < limit; i++) { - if (dataQueue.hasMore()) { - double fdata = dataQueue.readNextMonoDouble(getFramePeriod()); - outputs[i] = fdata * amplitudes[i]; - } else { - outputs[i] = 0.0; - if (dataQueue.testAndClearAutoStop()) { - autoStop(); - } - } - dataQueue.firePendingCallbacks(); - } - } - -} diff --git a/src/com/jsyn/unitgen/FixedRateMonoWriter.java b/src/com/jsyn/unitgen/FixedRateMonoWriter.java deleted file mode 100644 index c215c55..0000000 --- a/src/com/jsyn/unitgen/FixedRateMonoWriter.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * 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.unitgen; - -import com.jsyn.ports.UnitInputPort; - -/** - * Simple sample writer. Write one sample per audio frame with no interpolation. This can be used to - * record audio or to build delay lines. - * - * Note that you must call start() on this unit because it does not have an output for pulling data. - * - * @see FixedRateStereoWriter - * @author Phil Burk (C) 2009 Mobileer Inc - */ -public class FixedRateMonoWriter extends SequentialDataWriter { - - public FixedRateMonoWriter() { - addPort(input = new UnitInputPort("Input")); - } - - @Override - public void generate(int start, int limit) { - double[] inputs = input.getValues(); - - for (int i = start; i < limit; i++) { - if (dataQueue.hasMore()) { - double value = inputs[i]; - dataQueue.writeNextDouble(value); - } else { - if (dataQueue.testAndClearAutoStop()) { - autoStop(); - } - } - dataQueue.firePendingCallbacks(); - } - - } - -} diff --git a/src/com/jsyn/unitgen/FixedRateStereoReader.java b/src/com/jsyn/unitgen/FixedRateStereoReader.java deleted file mode 100644 index c5c00ce..0000000 --- a/src/com/jsyn/unitgen/FixedRateStereoReader.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * 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.unitgen; - -import com.jsyn.ports.UnitOutputPort; - -/** - * Simple stereo sample player. Play one sample per audio frame with no interpolation. - * - * @author Phil Burk (C) 2009 Mobileer Inc - */ -public class FixedRateStereoReader extends SequentialDataReader { - public FixedRateStereoReader() { - addPort(output = new UnitOutputPort(2, "Output")); - dataQueue.setNumChannels(2); - } - - @Override - public void generate(int start, int limit) { - double[] amplitudes = amplitude.getValues(); - double[] output0s = output.getValues(0); - double[] output1s = output.getValues(1); - - for (int i = start; i < limit; i++) { - if (dataQueue.hasMore()) { - dataQueue.beginFrame(getFramePeriod()); - double fdata = dataQueue.readCurrentChannelDouble(0); - // System.out.println("SampleReader_16F2: left = " + fdata ); - double amp = amplitudes[i]; - output0s[i] = fdata * amp; - fdata = dataQueue.readCurrentChannelDouble(1); - // System.out.println("SampleReader_16F2: right = " + fdata ); - output1s[i] = fdata * amp; - dataQueue.endFrame(); - } else { - output0s[i] = 0.0; - output1s[i] = 0.0; - if (dataQueue.testAndClearAutoStop()) { - autoStop(); - } - } - dataQueue.firePendingCallbacks(); - } - } -} diff --git a/src/com/jsyn/unitgen/FixedRateStereoWriter.java b/src/com/jsyn/unitgen/FixedRateStereoWriter.java deleted file mode 100644 index e4502f9..0000000 --- a/src/com/jsyn/unitgen/FixedRateStereoWriter.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * 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.unitgen; - -import com.jsyn.ports.UnitInputPort; - -/** - * Simple stereo sample writer. Write two samples per audio frame with no interpolation. This can be - * used to record audio or to build delay lines. - * - * Note that you must call start() on this unit because it does not have an output for pulling data. - * - * @see FixedRateMonoWriter - * @author Phil Burk (C) 2009 Mobileer Inc - */ -public class FixedRateStereoWriter extends SequentialDataWriter { - - public FixedRateStereoWriter() { - addPort(input = new UnitInputPort(2, "Input")); - dataQueue.setNumChannels(2); - } - - @Override - public void generate(int start, int limit) { - double[] input0s = input.getValues(0); - double[] input1s = input.getValues(1); - - for (int i = start; i < limit; i++) { - if (dataQueue.hasMore()) { - dataQueue.beginFrame(getFramePeriod()); - double value = input0s[i]; - dataQueue.writeCurrentChannelDouble(0, value); - value = input1s[i]; - dataQueue.writeCurrentChannelDouble(1, value); - dataQueue.endFrame(); - } else { - if (dataQueue.testAndClearAutoStop()) { - autoStop(); - } - } - dataQueue.firePendingCallbacks(); - } - - } - -} diff --git a/src/com/jsyn/unitgen/FourWayFade.java b/src/com/jsyn/unitgen/FourWayFade.java deleted file mode 100644 index c7fd22a..0000000 --- a/src/com/jsyn/unitgen/FourWayFade.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright 1997 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.unitgen; - -import com.jsyn.ports.UnitInputPort; -import com.jsyn.ports.UnitOutputPort; - -/** - * FourWayFade unit. - *

- * Mix inputs 0-3 based on the value of two fade ports. You can think of the four inputs arranged - * clockwise as follows. - *

- * - *

- *      input[0] ---- input[1]
- *        |             |
- *        |             |
- *        |             |
- *      input[3] ---- input[2]
- * 
- * - * The "fade" port has two parts. Fade[0] fades between the pair of inputs (0,3) and the pair of - * inputs (1,2). Fade[1] fades between the pair of inputs (0,1) and the pair of inputs (3,2). - * - *
- *    Fade[0]    Fade[1]    Output
- *      -1         -1       Input[3]
- *      -1         +1       Input[0]
- *      +1         -1       Input[2]
- *      +1         +1       Input[1]
- *
- *
- *      -----Fade[0]----->
- *
- *         A
- *         |
- *         |
- *      Fade[1]
- *         |
- *         |
- * 
- *

- * - * @author (C) 1997-2009 Phil Burk, Mobileer Inc - */ -public class FourWayFade extends UnitGenerator { - public UnitInputPort input; - public UnitInputPort fade; - public UnitOutputPort output; - - /* Define Unit Ports used by connect() and set(). */ - public FourWayFade() { - addPort(input = new UnitInputPort(4, "Input")); - addPort(fade = new UnitInputPort(2, "Fade")); - addPort(output = new UnitOutputPort()); - } - - @Override - public void generate(int start, int limit) { - double[] inputAs = input.getValues(0); - double[] inputBs = input.getValues(1); - double[] inputCs = input.getValues(2); - double[] inputDs = input.getValues(3); - double[] fadeLRs = fade.getValues(0); - double[] fadeFBs = fade.getValues(1); - double[] outputs = output.getValues(0); - - for (int i = start; i < limit; i++) { - // Scale and offset to 0.0 to 1.0 range. - double gainLR = (fadeLRs[i] * 0.5) + 0.5; - double temp = 1.0 - gainLR; - double mixFront = (inputAs[i] * temp) + (inputBs[i] * gainLR); - double mixBack = (inputDs[i] * temp) + (inputCs[i] * gainLR); - - double gainFB = (fadeFBs[i] * 0.5) + 0.5; - outputs[i] = (mixBack * (1.0 - gainFB)) + (mixFront * gainFB); - } - } -} diff --git a/src/com/jsyn/unitgen/FunctionEvaluator.java b/src/com/jsyn/unitgen/FunctionEvaluator.java deleted file mode 100644 index 0cc0c83..0000000 --- a/src/com/jsyn/unitgen/FunctionEvaluator.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * 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. - */ -/** - * Aug 26, 2009 - * com.jsyn.engine.units.TunableFilter.java - */ - -package com.jsyn.unitgen; - -import com.jsyn.data.Function; -import com.jsyn.ports.UnitFunctionPort; -import com.jsyn.ports.UnitInputPort; - -/** - * Convert an input value to an output value. The Function is typically implemented by looking up a - * value in a DoubleTable. But other implementations of Function can be used. Input typically ranges - * from -1.0 to +1.0. - * - *

- * 
- *     // A unit that will lookup the function.
- * 	FunctionEvaluator shaper = new FunctionEvaluator();
- * 	synth.add( shaper );
- * 	shaper.start();
- * 	// Define a custom function.
- * 	Function cuber = new Function()
- * 	{
- * 		public double evaluate( double x )
- * 		{
- * 			return x * x * x;
- * 		}
- * 	};
- * 	shaper.function.set(cuber);
- * 
- * 	shaper.input.set( 0.5 );
- *  
- * 
- * - * @author Phil Burk (C) 2009 Mobileer Inc - * @see Function - */ -public class FunctionEvaluator extends UnitFilter { - public UnitInputPort amplitude; - public UnitFunctionPort function; - - public FunctionEvaluator() { - addPort(amplitude = new UnitInputPort("Amplitude", 1.0)); - addPort(function = new UnitFunctionPort("Function")); - } - - @Override - public void generate(int start, int limit) { - double[] inputs = input.getValues(); - double[] amplitudes = amplitude.getValues(); - double[] outputs = output.getValues(); - Function functionObject = function.get(); - - for (int i = start; i < limit; i++) { - outputs[i] = functionObject.evaluate(inputs[i]) * amplitudes[i]; - } - - } -} diff --git a/src/com/jsyn/unitgen/FunctionOscillator.java b/src/com/jsyn/unitgen/FunctionOscillator.java deleted file mode 100644 index 30d32d5..0000000 --- a/src/com/jsyn/unitgen/FunctionOscillator.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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.unitgen; - -import com.jsyn.data.Function; -import com.jsyn.ports.UnitFunctionPort; - -/** - * Oscillator that uses a Function object to define the waveform. Note that a DoubleTable can be - * used as the Function. - * - * @author Phil Burk (C) 2009 Mobileer Inc - */ -public class FunctionOscillator extends UnitOscillator { - public UnitFunctionPort function; - - public FunctionOscillator() { - addPort(function = new UnitFunctionPort("Function")); - } - - @Override - public void generate(int start, int limit) { - double[] frequencies = frequency.getValues(); - double[] amplitudes = amplitude.getValues(); - double[] outputs = output.getValues(); - - Function functionObject = function.get(); - - // Variables have a single value. - double currentPhase = phase.getValue(); - - for (int i = start; i < limit; i++) { - // Generate sawtooth phasor to provide phase for function lookup. - double phaseIncrement = convertFrequencyToPhaseIncrement(frequencies[i]); - currentPhase = incrementWrapPhase(currentPhase, phaseIncrement); - double value = functionObject.evaluate(currentPhase); - outputs[i] = value * amplitudes[i]; - } - - // Value needs to be saved for next time. - phase.setValue(currentPhase); - } - -} diff --git a/src/com/jsyn/unitgen/Grain.java b/src/com/jsyn/unitgen/Grain.java deleted file mode 100644 index 812061c..0000000 --- a/src/com/jsyn/unitgen/Grain.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright 2011 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.unitgen; - -/** - * A single Grain that is normally created and controlled by a GrainFarm. - * - * @author Phil Burk (C) 2011 Mobileer Inc - */ -public class Grain implements GrainEnvelope { - private double frameRate; - private double amplitude = 1.0; - - private GrainSource source; - private GrainEnvelope envelope; - - public Grain(GrainSource source, GrainEnvelope envelope) { - this.source = source; - this.envelope = envelope; - } - - @Override - public double next() { - if (envelope.hasMoreValues()) { - double env = envelope.next(); - return source.next() * env * amplitude; - } else { - return 0.0; - } - } - - @Override - public boolean hasMoreValues() { - return envelope.hasMoreValues(); - } - - @Override - public void reset() { - source.reset(); - envelope.reset(); - } - - public void setRate(double rate) { - source.setRate(rate); - } - - @Override - public void setDuration(double duration) { - envelope.setDuration(duration); - } - - @Override - public double getFrameRate() { - return frameRate; - } - - @Override - public void setFrameRate(double frameRate) { - this.frameRate = frameRate; - source.setFrameRate(frameRate); - envelope.setFrameRate(frameRate); - } - - public double getAmplitude() { - return amplitude; - } - - public void setAmplitude(double amplitude) { - this.amplitude = amplitude; - } - - public GrainSource getSource() { - return source; - } -} diff --git a/src/com/jsyn/unitgen/GrainCommon.java b/src/com/jsyn/unitgen/GrainCommon.java deleted file mode 100644 index a7a04fc..0000000 --- a/src/com/jsyn/unitgen/GrainCommon.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * 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.unitgen; - -public class GrainCommon { - protected double frameRate; - - public double getFrameRate() { - return frameRate; - } - - public void setFrameRate(double frameRate) { - this.frameRate = frameRate; - } - - public void reset() { - } -} diff --git a/src/com/jsyn/unitgen/GrainEnvelope.java b/src/com/jsyn/unitgen/GrainEnvelope.java deleted file mode 100644 index e6ff24c..0000000 --- a/src/com/jsyn/unitgen/GrainEnvelope.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright 2011 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.unitgen; - -/** - * This envelope should start at 0.0, go up to 1.0 and then return to 0.0 in duration time. - * - * @author Phil Burk (C) 2011 Mobileer Inc - */ -public interface GrainEnvelope { - - double getFrameRate(); - - void setFrameRate(double frameRate); - - /** - * @return next amplitude value of envelope - */ - double next(); - - /** - * Are there any more values to be generated in the envelope? - * - * @return true if more - */ - boolean hasMoreValues(); - - /** - * Prepare to start a new envelope. - */ - void reset(); - - /** - * @param duration in seconds. - */ - void setDuration(double duration); - -} diff --git a/src/com/jsyn/unitgen/GrainFarm.java b/src/com/jsyn/unitgen/GrainFarm.java deleted file mode 100644 index 78179bc..0000000 --- a/src/com/jsyn/unitgen/GrainFarm.java +++ /dev/null @@ -1,178 +0,0 @@ -/* - * Copyright 2011 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.unitgen; - -import com.jsyn.ports.UnitInputPort; -import com.jsyn.ports.UnitOutputPort; -import com.jsyn.util.PseudoRandom; - -/** - * A unit generator that generates a cloud of sound using multiple Grains. Special thanks to my - * friend Ross Bencina for his excellent article on Granular Synthesis. Several of his ideas are - * reflected in this architecture. "Implementing Real-Time Granular Synthesis" by Ross Bencina, - * Audio Anecdotes III, 2001. - * - *

-   synth.add( sampleGrainFarm = new GrainFarm() );
-   grainFarm.allocate( NUM_GRAINS );
-
- * - * @author Phil Burk (C) 2011 Mobileer Inc - * @see Grain - * @see GrainSourceSine - * @see RaisedCosineEnvelope - */ -public class GrainFarm extends UnitGenerator implements UnitSource { - /** A scaler for playback rate. Nominally 1.0. */ - public UnitInputPort rate; - public UnitInputPort rateRange; - public UnitInputPort amplitude; - public UnitInputPort amplitudeRange; - public UnitInputPort density; - public UnitInputPort duration; - public UnitInputPort durationRange; - public UnitOutputPort output; - - PseudoRandom randomizer; - private GrainState[] states; - private double countScaler = 1.0; - private final GrainScheduler scheduler = new StochasticGrainScheduler(); - - public GrainFarm() { - randomizer = new PseudoRandom(); - addPort(rate = new UnitInputPort("Rate", 1.0)); - addPort(amplitude = new UnitInputPort("Amplitude", 1.0)); - addPort(duration = new UnitInputPort("Duration", 0.01)); - addPort(rateRange = new UnitInputPort("RateRange", 0.0)); - addPort(amplitudeRange = new UnitInputPort("AmplitudeRange", 0.0)); - addPort(durationRange = new UnitInputPort("DurationRange", 0.0)); - addPort(density = new UnitInputPort("Density", 0.1)); - addPort(output = new UnitOutputPort()); - } - - private class GrainState { - Grain grain; - int countdown; - double lastDuration; - final static int STATE_IDLE = 0; - final static int STATE_GAP = 1; - final static int STATE_RUNNING = 2; - int state = STATE_IDLE; - private double gapError; - - public double next(int i) { - double output = 0.0; - if (state == STATE_RUNNING) { - if (grain.hasMoreValues()) { - output = grain.next(); - } else { - startGap(i); - } - } else if (state == STATE_GAP) { - if (countdown > 0) { - countdown -= 1; - } else if (countdown == 0) { - state = STATE_RUNNING; - grain.reset(); - - setupGrain(grain, i); - - double dur = nextDuration(i); - grain.setDuration(dur); - } - } else if (state == STATE_IDLE) { - nextDuration(i); // initialize lastDuration - startGap(i); - } - return output; - } - - private double nextDuration(int i) { - double dur = duration.getValues()[i]; - dur = scheduler.nextDuration(dur); - lastDuration = dur; - return dur; - } - - private void startGap(int i) { - state = STATE_GAP; - double dens = density.getValues()[i]; - double gap = scheduler.nextGap(lastDuration, dens) * getFrameRate(); - gap += gapError; - countdown = (int) gap; - gapError = gap - countdown; - } - } - - public void setGrainArray(Grain[] grains) { - countScaler = 1.0 / grains.length; - states = new GrainState[grains.length]; - for (int i = 0; i < states.length; i++) { - states[i] = new GrainState(); - states[i].grain = grains[i]; - grains[i].setFrameRate(getSynthesisEngine().getFrameRate()); - } - } - - public void setupGrain(Grain grain, int i) { - double temp = rate.getValues()[i] * calculateOctaveScaler(rateRange.getValues()[i]); - grain.setRate(temp); - - // Scale the amplitude range so that we never go above - // original amplitude. - double base = amplitude.getValues()[i]; - double offset = base * Math.random() * amplitudeRange.getValues()[i]; - grain.setAmplitude(base - offset); - } - - public void allocate(int numGrains) { - Grain[] grainArray = new Grain[numGrains]; - for (int i = 0; i < numGrains; i++) { - Grain grain = new Grain(new GrainSourceSine(), new RaisedCosineEnvelope()); - grainArray[i] = grain; - } - setGrainArray(grainArray); - } - - @Override - public UnitOutputPort getOutput() { - return output; - } - - private double calculateOctaveScaler(double rangeValue) { - double octaveRange = 0.5 * randomizer.nextRandomDouble() * rangeValue; - return Math.pow(2.0, octaveRange); - } - - @Override - public void generate(int start, int limit) { - double[] outputs = output.getValues(); - double[] amplitudes = amplitude.getValues(); - // double frp = getSynthesisEngine().getFramePeriod(); - for (int i = start; i < limit; i++) { - double result = 0.0; - - // Mix the grains together. - for (GrainState grainState : states) { - result += grainState.next(i); - } - - outputs[i] = result * amplitudes[i] * countScaler; - } - - } -} diff --git a/src/com/jsyn/unitgen/GrainScheduler.java b/src/com/jsyn/unitgen/GrainScheduler.java deleted file mode 100644 index df9c25e..0000000 --- a/src/com/jsyn/unitgen/GrainScheduler.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2011 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.unitgen; - -/** - * Defines a class that can schedule the execution of Grains in a GrainFarm. This is mostly for - * internal use. - * - * @author Phil Burk (C) 2011 Mobileer Inc - */ -public interface GrainScheduler { - - /** - * Calculate time in seconds for the next gap between grains. - * - * @param duration - * @param density - * @return seconds before next grain - */ - double nextGap(double duration, double density); - - /** - * Calculate duration in seconds for the next grains. - * - * @param suggestedDuration - * @return duration of grain seconds - */ - double nextDuration(double suggestedDuration); - -} diff --git a/src/com/jsyn/unitgen/GrainSource.java b/src/com/jsyn/unitgen/GrainSource.java deleted file mode 100644 index 1d5c522..0000000 --- a/src/com/jsyn/unitgen/GrainSource.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2011 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.unitgen; - -/** - * Defines classes that can provide the signal inside a Grain. - * - * @author Phil Burk (C) 2011 Mobileer Inc - */ -public interface GrainSource { - double getFrameRate(); - - void setFrameRate(double frameRate); - - /** Generate one more value or the source signal. */ - double next(); - - /** Reset the internal phase of the grain. */ - void reset(); - - void setRate(double rate); -} diff --git a/src/com/jsyn/unitgen/GrainSourceSine.java b/src/com/jsyn/unitgen/GrainSourceSine.java deleted file mode 100644 index 0af9cbd..0000000 --- a/src/com/jsyn/unitgen/GrainSourceSine.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2011 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.unitgen; - -/** - * A simple sine wave generator for a Grain. This uses the same fast Taylor expansion that the - * SineOscillator uses. - * - * @author Phil Burk (C) 2011 Mobileer Inc - */ -public class GrainSourceSine extends GrainCommon implements GrainSource { - protected double phase; - protected double phaseIncrement; - - public GrainSourceSine() { - setRate(1.0); - } - - public void setPhaseIncrement(double phaseIncrement) { - this.phaseIncrement = phaseIncrement; - } - - @Override - public double next() { - phase += phaseIncrement; - if (phase > 1.0) { - phase -= 2.0; - } - return SineOscillator.fastSin(phase); - } - - @Override - public void setRate(double rate) { - setPhaseIncrement(rate * 0.1 / Math.PI); - } - -} diff --git a/src/com/jsyn/unitgen/IFFT.java b/src/com/jsyn/unitgen/IFFT.java deleted file mode 100644 index 307acd2..0000000 --- a/src/com/jsyn/unitgen/IFFT.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2013 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.unitgen; - -/** - * Periodically transform the complex input spectrum using an IFFT to a complex signal stream. - * - * @author Phil Burk (C) 2013 Mobileer Inc - * @version 016 - * @see FFT - */ -public class IFFT extends FFTBase { - - public IFFT() { - super(); - } - - @Override - protected int getSign() { - return -1; // -1 for IFFT - } -} diff --git a/src/com/jsyn/unitgen/ImpulseOscillator.java b/src/com/jsyn/unitgen/ImpulseOscillator.java deleted file mode 100644 index 8c676f3..0000000 --- a/src/com/jsyn/unitgen/ImpulseOscillator.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * 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.unitgen; - -/** - * Narrow impulse oscillator. An impulse is only one sample wide. It is useful for pinging filters - * or generating an "impulse response". - * - * @author Phil Burk (C) 2009 Mobileer Inc - */ -public class ImpulseOscillator extends UnitOscillator { - - @Override - public void generate(int start, int limit) { - double[] frequencies = frequency.getValues(); - double[] amplitudes = amplitude.getValues(); - double[] outputs = output.getValues(); - - // Variables have a single value. - double currentPhase = phase.getValue(); - - double inverseNyquist = synthesisEngine.getInverseNyquist(); - - for (int i = start; i < limit; i++) { - /* Generate sawtooth phasor to provide phase for impulse generation. */ - double phaseIncrement = frequencies[i] * inverseNyquist; - currentPhase += phaseIncrement; - - double ampl = amplitudes[i]; - double result = 0.0; - if (currentPhase >= 1.0) { - currentPhase -= 2.0; - result = ampl; - } else if (currentPhase < -1.0) { - currentPhase += 2.0; - result = ampl; - } - outputs[i] = result; - } - - // Value needs to be saved for next time. - phase.setValue(currentPhase); - } - -} diff --git a/src/com/jsyn/unitgen/ImpulseOscillatorBL.java b/src/com/jsyn/unitgen/ImpulseOscillatorBL.java deleted file mode 100644 index 23686b8..0000000 --- a/src/com/jsyn/unitgen/ImpulseOscillatorBL.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * 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.unitgen; - -import com.jsyn.engine.MultiTable; - -/** - * Impulse oscillator created by differentiating a sawtoothBL. A band limited impulse is very narrow - * but is slightly wider than one sample. - * - * @author Phil Burk (C) 2009 Mobileer Inc - */ -public class ImpulseOscillatorBL extends SawtoothOscillatorBL { - private double previous = 0.0; - - @Override - protected double generateBL(MultiTable multiTable, double currentPhase, - double positivePhaseIncrement, double flevel, int i) { - double saw = multiTable.calculateSawtooth(currentPhase, positivePhaseIncrement, flevel); - double result = previous - saw; - previous = saw; - return result; - } - -} diff --git a/src/com/jsyn/unitgen/Integrate.java b/src/com/jsyn/unitgen/Integrate.java deleted file mode 100644 index b5beea9..0000000 --- a/src/com/jsyn/unitgen/Integrate.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright 1997 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.unitgen; - -import com.jsyn.ports.UnitInputPort; -import com.jsyn.ports.UnitOutputPort; - -/** - * IntegrateUnit unit. - *

- * Output accumulated sum of the input signal. This can be used to transform one signal into - * another, or to generate ramps between the limits by setting the input signal positive or - * negative. For a "leaky integrator" use a FilterOnePoleOneZero. - *

- * - *

- * output = output + input;
- * if (output < lowerLimit)
- *     output = lowerLimit;
- * else if (output > upperLimit)
- *     output = upperLimit;
- * 
- * - * @author (C) 1997-2011 Phil Burk, Mobileer Inc - * @see FilterOnePoleOneZero - */ -public class Integrate extends UnitGenerator { - public UnitInputPort input; - /** - * Output will be stopped internally from going below this value. Default is -1.0. - */ - public UnitInputPort lowerLimit; - /** - * Output will be stopped internally from going above this value. Default is +1.0. - */ - public UnitInputPort upperLimit; - public UnitOutputPort output; - - private double accum; - - /* Define Unit Ports used by connect() and set(). */ - public Integrate() { - addPort(input = new UnitInputPort("Input")); - addPort(lowerLimit = new UnitInputPort("LowerLimit", -1.0)); - addPort(upperLimit = new UnitInputPort("UpperLimit", 1.0)); - addPort(output = new UnitOutputPort("Output")); - } - - @Override - public void generate(int start, int limit) { - double[] inputs = input.getValues(); - double[] lowerLimits = lowerLimit.getValues(); - double[] upperLimits = upperLimit.getValues(); - double[] outputs = output.getValues(); - - for (int i = start; i < limit; i++) { - accum += inputs[i]; // INTEGRATE - - // clip to limits - if (accum > upperLimits[i]) - accum = upperLimits[i]; - else if (accum < lowerLimits[i]) - accum = lowerLimits[i]; - - outputs[i] = accum; - } - } -} diff --git a/src/com/jsyn/unitgen/InterpolatingDelay.java b/src/com/jsyn/unitgen/InterpolatingDelay.java deleted file mode 100644 index 24de4f9..0000000 --- a/src/com/jsyn/unitgen/InterpolatingDelay.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright 1997 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.unitgen; - -import com.jsyn.ports.UnitInputPort; - -/** - * InterpolatingDelay - *

- * InterpolatingDelay provides a variable time delay with an input and output. The internal data - * format is 32-bit floating point. The amount of delay can be varied from 0.0 to a time in seconds - * corresponding to the numFrames allocated. The fractional delay values are calculated by linearly - * interpolating between adjacent values in the delay line. - *

- * This unit can be used to implement time varying delay effects such as a flanger or a chorus. It - * can also be used to implement physical models of acoustic instruments, or other tunable delay - * based resonant systems. - *

- * - * @author (C) 1997-2011 Phil Burk, Mobileer Inc - * @see Delay - */ - -public class InterpolatingDelay extends UnitFilter { - /** - * Delay time in seconds. This value will converted to frames and clipped between zero and the - * numFrames value passed to allocate(). The minimum and default delay time is 0.0. - */ - public UnitInputPort delay; - - private float[] buffer; - private int cursor; - private int numFrames; - - public InterpolatingDelay() { - addPort(delay = new UnitInputPort("Delay")); - } - - /** - * Allocate memory for the delay buffer. For a 2 second delay at 44100 Hz sample rate you will - * need at least 88200 samples. - * - * @param numFrames size of the float array to hold the delayed samples - */ - public void allocate(int numFrames) { - this.numFrames = numFrames; - // Allocate extra frame for guard point to speed up interpolation. - buffer = new float[numFrames + 1]; - } - - @Override - public void generate(int start, int limit) { - double[] inputs = input.getValues(); - double[] outputs = output.getValues(); - double[] delays = delay.getValues(); - - for (int i = start; i < limit; i++) { - // This should be at the beginning of the loop - // because the guard point should == buffer[0]. - if (cursor == numFrames) { - // Write guard point! Must allocate one extra sample. - buffer[numFrames] = (float) inputs[i]; - cursor = 0; - } - - buffer[cursor] = (float) inputs[i]; - - /* Convert delay time to a clipped frame offset. */ - double delayFrames = delays[i] * getFrameRate(); - - // Clip to zero delay. - if (delayFrames <= 0.0) { - outputs[i] = buffer[cursor]; - } else { - // Clip to maximum delay. - if (delayFrames >= numFrames) { - delayFrames = numFrames - 1; - } - - // Calculate fractional index into delay buffer. - double readIndex = cursor - delayFrames; - if (readIndex < 0.0) { - readIndex += numFrames; - } - // setup for interpolation. - // We know readIndex is > 0 so we do not need to call floor(). - int iReadIndex = (int) readIndex; - double frac = readIndex - iReadIndex; - - // Get adjacent values relying on guard point to prevent overflow. - double val0 = buffer[iReadIndex]; - double val1 = buffer[iReadIndex + 1]; - - // Interpolate new value. - outputs[i] = val0 + (frac * (val1 - val0)); - } - - cursor += 1; - } - - } - -} diff --git a/src/com/jsyn/unitgen/Latch.java b/src/com/jsyn/unitgen/Latch.java deleted file mode 100644 index 0518f69..0000000 --- a/src/com/jsyn/unitgen/Latch.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 1997 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.unitgen; - -import com.jsyn.ports.UnitInputPort; - -/** - * Latch or hold an input value. - *

- * Pass a value unchanged if gate true, otherwise output held value. - *

- * output = ( gate > 0.0 ) ? input : previous_output; - * - * @author (C) 1997-2010 Phil Burk, Mobileer Inc - * @see EdgeDetector - */ -public class Latch extends UnitFilter { - public UnitInputPort gate; - private double held; - - /* Define Unit Ports used by connect() and set(). */ - public Latch() { - addPort(gate = new UnitInputPort("Gate")); - } - - @Override - public void generate(int start, int limit) { - double[] inputs = input.getValues(); - double[] gates = gate.getValues(); - double[] outputs = output.getValues(); - - for (int i = start; i < limit; i++) { - if (gates[i] > 0.0) { - held = inputs[i]; - } - outputs[i] = held; - } - } -} diff --git a/src/com/jsyn/unitgen/LatchZeroCrossing.java b/src/com/jsyn/unitgen/LatchZeroCrossing.java deleted file mode 100644 index 9e6c011..0000000 --- a/src/com/jsyn/unitgen/LatchZeroCrossing.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright 2010 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.unitgen; - -import com.jsyn.ports.UnitInputPort; -import com.jsyn.ports.UnitOutputPort; - -/** - * Latches when input crosses zero. - *

- * Pass a value unchanged if gate true, otherwise pass input unchanged until input crosses zero then - * output zero. This can be used to turn off a sound at a zero crossing so there is no pop. - *

- * - * @author (C) 2010 Phil Burk, Mobileer Inc - * @see Latch - * @see Minimum - */ -public class LatchZeroCrossing extends UnitGenerator { - public UnitInputPort input; - public UnitInputPort gate; - public UnitOutputPort output; - private double held; - private boolean crossed; - - /* Define Unit Ports used by connect() and set(). */ - public LatchZeroCrossing() { - addPort(input = new UnitInputPort("Input")); - addPort(gate = new UnitInputPort("Gate", 1.0)); - addPort(output = new UnitOutputPort("Output")); - } - - @Override - public void generate(int start, int limit) { - double[] inputs = input.getValues(); - double[] gates = gate.getValues(); - double[] outputs = output.getValues(); - - for (int i = start; i < limit; i++) { - double current = inputs[i]; - if (gates[i] > 0.0) { - held = current; - crossed = false; - } else { - // If we haven't already seen a zero crossing then look for one. - if (!crossed) { - if ((held * current) <= 0.0) { - held = 0.0; - crossed = true; - } else { - held = current; - } - } - } - outputs[i] = held; - } - } -} diff --git a/src/com/jsyn/unitgen/LineIn.java b/src/com/jsyn/unitgen/LineIn.java deleted file mode 100644 index aeef965..0000000 --- a/src/com/jsyn/unitgen/LineIn.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * 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.unitgen; - -import com.jsyn.Synthesizer; -import com.jsyn.ports.UnitOutputPort; - -/** - * External audio input is sent to the output of this unit. The LineIn provides a stereo signal - * containing channels 0 and 1. For LineIn to work you must call the Synthesizer start() method with - * numInputChannels > 0. - * - * @author Phil Burk (C) 2009 Mobileer Inc - * @see Synthesizer - * @see ChannelIn - * @see LineOut - */ -public class LineIn extends UnitGenerator { - public UnitOutputPort output; - - public LineIn() { - addPort(output = new UnitOutputPort(2, "Output")); - } - - @Override - public void generate(int start, int limit) { - double[] outputs0 = output.getValues(0); - double[] outputs1 = output.getValues(1); - double[] buffer0 = synthesisEngine.getInputBuffer(0); - double[] buffer1 = synthesisEngine.getInputBuffer(1); - for (int i = start; i < limit; i++) { - outputs0[i] = buffer0[i]; - outputs1[i] = buffer1[i]; - } - } - -} diff --git a/src/com/jsyn/unitgen/LineOut.java b/src/com/jsyn/unitgen/LineOut.java deleted file mode 100644 index 29c8ce7..0000000 --- a/src/com/jsyn/unitgen/LineOut.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * 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.unitgen; - -import com.jsyn.ports.UnitInputPort; - -/** - * Input audio is sent to the external audio output device. - * - * @author Phil Burk (C) 2009 Mobileer Inc - */ -public class LineOut extends UnitGenerator implements UnitSink { - public UnitInputPort input; - - public LineOut() { - addPort(input = new UnitInputPort(2, "Input")); - } - - @Override - public void generate(int start, int limit) { - double[] inputs0 = input.getValues(0); - double[] inputs1 = input.getValues(1); - double[] buffer0 = synthesisEngine.getOutputBuffer(0); - double[] buffer1 = synthesisEngine.getOutputBuffer(1); - for (int i = start; i < limit; i++) { - buffer0[i] += inputs0[i]; - buffer1[i] += inputs1[i]; - } - } - - /** - * This unit won't do anything unless you start() it. - */ - @Override - public boolean isStartRequired() { - return true; - } - - @Override - public UnitInputPort getInput() { - return input; - } -} diff --git a/src/com/jsyn/unitgen/LinearRamp.java b/src/com/jsyn/unitgen/LinearRamp.java deleted file mode 100644 index cad53d5..0000000 --- a/src/com/jsyn/unitgen/LinearRamp.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright 1997 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.unitgen; - -import com.jsyn.ports.UnitInputPort; -import com.jsyn.ports.UnitVariablePort; - -/** - * Output approaches Input linearly. - *

- * When you change the value of the input port, the ramp will start changing from its current output - * value toward the value of input. An internal phase value will go from 0.0 to 1.0 at a rate - * controlled by time. When the internal phase reaches 1.0, the output will equal input. - *

- * - * @author (C) 1997 Phil Burk, SoftSynth.com - * @see ExponentialRamp - * @see AsymptoticRamp - * @see ContinuousRamp - */ -public class LinearRamp extends UnitFilter { - /** Time in seconds to get to the input value. */ - public UnitInputPort time; - public UnitVariablePort current; - - private double source; - private double phase; - private double target; - private double timeHeld = 0.0; - private double rate = 1.0; - - public LinearRamp() { - addPort(time = new UnitInputPort("Time")); - addPort(current = new UnitVariablePort("Current")); - } - - @Override - public void generate(int start, int limit) { - double[] outputs = output.getValues(); - double currentInput = input.getValues()[0]; - double currentValue = current.getValue(); - - // If input has changed, start new segment. - // Equality check is OK because we set them exactly equal below. - if (currentInput != target) - { - source = currentValue; - phase = 0.0; - target = currentInput; - } - - if (currentValue == target) { - // at end of ramp - for (int i = start; i < limit; i++) { - outputs[i] = currentValue; - } - } else { - // in middle of ramp - double currentTime = time.getValues()[0]; - // Has time changed? - if (currentTime != timeHeld) { - rate = convertTimeToRate(currentTime); - timeHeld = currentTime; - } - - for (int i = start; i < limit; i++) { - if (phase < 1.0) { - /* Interpolate current. */ - currentValue = source + (phase * (target - source)); - phase += rate; - } else { - currentValue = target; - } - outputs[i] = currentValue; - } - } - - current.setValue(currentValue); - } -} diff --git a/src/com/jsyn/unitgen/Maximum.java b/src/com/jsyn/unitgen/Maximum.java deleted file mode 100644 index 296e5da..0000000 --- a/src/com/jsyn/unitgen/Maximum.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * 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.unitgen; - -/** - * - Output largest of inputA or inputB. - * - *

- * output = (inputA > InputB) ? inputA : InputB;
- * 
- * - * @author Phil Burk (C) 2009 Mobileer Inc - * @version 016 - * @see Minimum - */ -public class Maximum extends UnitBinaryOperator { - @Override - public void generate(int start, int limit) { - double[] aValues = inputA.getValues(); - double[] bValues = inputB.getValues(); - double[] outputs = output.getValues(); - - for (int i = start; i < limit; i++) { - outputs[i] = (aValues[i] > bValues[i]) ? aValues[i] : bValues[i]; - } - } -} diff --git a/src/com/jsyn/unitgen/Minimum.java b/src/com/jsyn/unitgen/Minimum.java deleted file mode 100644 index 046387e..0000000 --- a/src/com/jsyn/unitgen/Minimum.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * 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.unitgen; - -/** - * - Output smallest of inputA or inputB. - * - *
- * output = (inputA < InputB) ? inputA : InputB;
- * 
- * - * @author Phil Burk (C) 2009 Mobileer Inc - * @version 016 - * @see Maximum - */ -public class Minimum extends UnitBinaryOperator { - - @Override - public void generate(int start, int limit) { - double[] aValues = inputA.getValues(); - double[] bValues = inputB.getValues(); - double[] outputs = output.getValues(); - - for (int i = start; i < limit; i++) { - outputs[i] = (aValues[i] < bValues[i]) ? aValues[i] : bValues[i]; - } - } -} diff --git a/src/com/jsyn/unitgen/MixerMono.java b/src/com/jsyn/unitgen/MixerMono.java deleted file mode 100644 index f4c7d7d..0000000 --- a/src/com/jsyn/unitgen/MixerMono.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright 2014 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.unitgen; - -import com.jsyn.ports.UnitInputPort; -import com.jsyn.ports.UnitOutputPort; - -/** - * Multi-channel mixer with mono output and master amplitude. - * - * @author Phil Burk (C) 2014 Mobileer Inc - * @see MixerMonoRamped - * @see MixerStereo - */ -public class MixerMono extends UnitGenerator implements UnitSink, UnitSource { - public UnitInputPort input; - /** - * Linear gain for the corresponding input. - */ - public UnitInputPort gain; - /** - * Master gain control. - */ - public UnitInputPort amplitude; - public UnitOutputPort output; - - public MixerMono(int numInputs) { - addPort(input = new UnitInputPort(numInputs, "Input")); - addPort(gain = new UnitInputPort(numInputs, "Gain", 1.0)); - addPort(amplitude = new UnitInputPort("Amplitude", 1.0)); - addPort(output = new UnitOutputPort(getNumOutputs(), "Output")); - } - - public int getNumOutputs() { - return 1; - } - - @Override - public void generate(int start, int limit) { - double[] amplitudes = amplitude.getValues(0); - double[] outputs = output.getValues(0); - for (int i = start; i < limit; i++) { - double sum = 0; - for (int n = 0; n < input.getNumParts(); n++) { - double[] inputs = input.getValues(n); - double[] gains = gain.getValues(n); - sum += inputs[i] * gains[i]; - } - outputs[i] = sum * amplitudes[i]; - } - } - - @Override - public UnitInputPort getInput() { - return input; - } - - @Override - public UnitOutputPort getOutput() { - return output; - } - -} diff --git a/src/com/jsyn/unitgen/MixerMonoRamped.java b/src/com/jsyn/unitgen/MixerMonoRamped.java deleted file mode 100644 index 30f5342..0000000 --- a/src/com/jsyn/unitgen/MixerMonoRamped.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2014 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.unitgen; - -/** - * Similar to MixerMono but the gain and amplitude ports are smoothed using short linear ramps. So - * you can control them with knobs and not hear any zipper noise. - * - * @author Phil Burk (C) 2014 Mobileer Inc - */ -public class MixerMonoRamped extends MixerMono { - private Unzipper[] unzippers; - private Unzipper amplitudeUnzipper; - - public MixerMonoRamped(int numInputs) { - super(numInputs); - unzippers = new Unzipper[numInputs]; - for (int i = 0; i < numInputs; i++) { - unzippers[i] = new Unzipper(); - } - amplitudeUnzipper = new Unzipper(); - } - - @Override - public void generate(int start, int limit) { - double[] amplitudes = amplitude.getValues(0); - double[] outputs = output.getValues(0); - for (int i = start; i < limit; i++) { - double sum = 0; - for (int n = 0; n < input.getNumParts(); n++) { - double[] inputs = input.getValues(n); - double[] gains = gain.getValues(n); - double smoothGain = unzippers[n].smooth(gains[i]); - sum += inputs[i] * smoothGain; - } - outputs[i] = sum * amplitudeUnzipper.smooth(amplitudes[i]); - } - } - -} diff --git a/src/com/jsyn/unitgen/MixerStereo.java b/src/com/jsyn/unitgen/MixerStereo.java deleted file mode 100644 index 218546e..0000000 --- a/src/com/jsyn/unitgen/MixerStereo.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright 2014 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.unitgen; - -import com.jsyn.ports.UnitInputPort; - -/** - * Mixer with monophonic inputs and two channels of output. Each signal can be panned left or right - * using an equal power curve. The "left" signal will be on output part zero. The "right" signal - * will be on output part one. - * - * @author Phil Burk (C) 2014 Mobileer Inc - * @see MixerMono - * @see MixerStereoRamped - */ -public class MixerStereo extends MixerMono { - /** - * Set to -1.0 for all left channel, 0.0 for center, or +1.0 for all right. Or anywhere in - * between. - */ - public UnitInputPort pan; - protected PanTracker[] panTrackers; - - static class PanTracker { - double previousPan = Double.MAX_VALUE; // so we update immediately - double leftGain; - double rightGain; - - public void update(double pan) { - if (pan != previousPan) { - // fastSine range is -1.0 to +1.0 for full cycle. - // We want a quarter cycle. So map -1.0 to +1.0 into 0.0 to 0.5 - double phase = pan * 0.25 + 0.25; - leftGain = SineOscillator.fastSin(0.5 - phase); - rightGain = SineOscillator.fastSin(phase); - previousPan = pan; - } - } - } - - public MixerStereo(int numInputs) { - super(numInputs); - addPort(pan = new UnitInputPort(numInputs, "Pan")); - pan.setup(-1.0, 0.0, 1.0); - panTrackers = new PanTracker[numInputs]; - for (int i = 0; i < numInputs; i++) { - panTrackers[i] = new PanTracker(); - } - } - - @Override - public int getNumOutputs() { - return 2; - } - - @Override - public void generate(int start, int limit) { - double[] amplitudes = amplitude.getValues(0); - double[] outputs0 = output.getValues(0); - double[] outputs1 = output.getValues(1); - for (int i = start; i < limit; i++) { - double sum0 = 0.0; - double sum1 = 0.0; - for (int n = 0; n < input.getNumParts(); n++) { - double[] inputs = input.getValues(n); - double[] gains = gain.getValues(n); - double[] pans = pan.getValues(n); - PanTracker panTracker = panTrackers[n]; - panTracker.update(pans[i]); - double scaledInput = inputs[i] * gains[i]; - sum0 += scaledInput * panTracker.leftGain; - sum1 += scaledInput * panTracker.rightGain; - } - double amp = amplitudes[i]; - outputs0[i] = sum0 * amp; - outputs1[i] = sum1 * amp; - } - } - -} diff --git a/src/com/jsyn/unitgen/MixerStereoRamped.java b/src/com/jsyn/unitgen/MixerStereoRamped.java deleted file mode 100644 index 6f3bfcc..0000000 --- a/src/com/jsyn/unitgen/MixerStereoRamped.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2014 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.unitgen; - -/** - * Similar to MixerStereo but the gain, pan and amplitude ports are smoothed using short linear - * ramps. So you can control them with knobs and not hear any zipper noise. - * - * @author Phil Burk (C) 2014 Mobileer Inc - */ -public class MixerStereoRamped extends MixerStereo { - private Unzipper[] gainUnzippers; - private Unzipper[] panUnzippers; - private Unzipper amplitudeUnzipper; - - public MixerStereoRamped(int numInputs) { - super(numInputs); - gainUnzippers = new Unzipper[numInputs]; - for (int i = 0; i < numInputs; i++) { - gainUnzippers[i] = new Unzipper(); - } - panUnzippers = new Unzipper[numInputs]; - for (int i = 0; i < numInputs; i++) { - panUnzippers[i] = new Unzipper(); - } - amplitudeUnzipper = new Unzipper(); - } - - @Override - public void generate(int start, int limit) { - double[] amplitudes = amplitude.getValues(0); - double[] outputs0 = output.getValues(0); - double[] outputs1 = output.getValues(1); - for (int i = start; i < limit; i++) { - double sum0 = 0; - double sum1 = 0; - for (int n = 0; n < input.getNumParts(); n++) { - double[] inputs = input.getValues(n); - double[] gains = gain.getValues(n); - double[] pans = pan.getValues(n); - - PanTracker panTracker = panTrackers[n]; - double smoothPan = panUnzippers[n].smooth(pans[i]); - panTracker.update(smoothPan); - - double smoothGain = gainUnzippers[n].smooth(gains[i]); - double scaledInput = inputs[i] * smoothGain; - sum0 += scaledInput * panTracker.leftGain; - sum1 += scaledInput * panTracker.rightGain; - } - double amp = amplitudeUnzipper.smooth(amplitudes[i]); - outputs0[i] = sum0 * amp; - outputs1[i] = sum1 * amp; - } - } - -} diff --git a/src/com/jsyn/unitgen/MonoStreamWriter.java b/src/com/jsyn/unitgen/MonoStreamWriter.java deleted file mode 100644 index 283af81..0000000 --- a/src/com/jsyn/unitgen/MonoStreamWriter.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * 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.unitgen; - -import java.io.IOException; - -import com.jsyn.io.AudioOutputStream; -import com.jsyn.ports.UnitInputPort; - -/** - * Write one sample per audio frame to an AudioOutputStream with no interpolation. - * - * Note that you must call start() on this unit because it does not have an output for pulling data. - * - * @author Phil Burk (C) 2009 Mobileer Inc - */ -public class MonoStreamWriter extends UnitStreamWriter { - public MonoStreamWriter() { - addPort(input = new UnitInputPort("Input")); - } - - @Override - public void generate(int start, int limit) { - double[] inputs = input.getValues(); - AudioOutputStream output = outputStream; - if (output != null) { - int count = limit - start; - try { - output.write(inputs, start, count); - } catch (IOException e) { - } - } - } - -} diff --git a/src/com/jsyn/unitgen/MorphingOscillatorBL.java b/src/com/jsyn/unitgen/MorphingOscillatorBL.java deleted file mode 100644 index 7ca440d..0000000 --- a/src/com/jsyn/unitgen/MorphingOscillatorBL.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * 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.unitgen; - -import com.jsyn.engine.MultiTable; -import com.jsyn.ports.UnitInputPort; - -/** - * Oscillator that can change its shape from sine to sawtooth to pulse. - * - * @author Phil Burk (C) 2016 Mobileer Inc - */ -public class MorphingOscillatorBL extends PulseOscillatorBL { - /** - * Controls the shape of the waveform. - * The shape varies continuously from a sine wave at -1.0, - * to a sawtooth at 0.0 to a pulse wave at 1.0. - */ - public UnitInputPort shape; - - public MorphingOscillatorBL() { - addPort(shape = new UnitInputPort("Shape")); - shape.setMinimum(-1.0); - shape.setMaximum(1.0); - } - - @Override - protected double generateBL(MultiTable multiTable, double currentPhase, - double positivePhaseIncrement, double flevel, int i) { - double[] shapes = shape.getValues(); - double shape = shapes[i]; - - if (shape < 0.0) { - // Squeeze flevel towards the pure sine table. - flevel += flevel * shape; - return multiTable.calculateSawtooth(currentPhase, positivePhaseIncrement, flevel); - } else { - double[] widths = width.getValues(); - double width = widths[i]; - width = (width > 0.999) ? 0.999 : ((width < -0.999) ? -0.999 : width); - - double val1 = multiTable.calculateSawtooth(currentPhase, positivePhaseIncrement, flevel); - // Generate second sawtooth so we can add them together. - double phase2 = currentPhase + 1.0 - width; // 180 degrees out of phase - if (phase2 >= 1.0) { - phase2 -= 2.0; - } - double val2 = multiTable.calculateSawtooth(phase2, positivePhaseIncrement, flevel); - - /* - * Need to adjust amplitude based on positive phaseInc. little less than half at - * Nyquist/2.0! - */ - double scale = 1.0 - positivePhaseIncrement; - return scale * (val1 - ((val2 + width) * shape)); // apply shape morphing - } - } -} diff --git a/src/com/jsyn/unitgen/MultiPassThrough.java b/src/com/jsyn/unitgen/MultiPassThrough.java deleted file mode 100644 index 9125fc3..0000000 --- a/src/com/jsyn/unitgen/MultiPassThrough.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright 2011 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.unitgen; - -import com.jsyn.ports.UnitInputPort; -import com.jsyn.ports.UnitOutputPort; - -/** - * Pass the input through to the output unchanged. This is often used for distributing a signal to - * multiple ports inside a circuit. It can also be used as a summing node, in other words, a mixer. - * - * This is just like PassThrough except the input and output ports have multiple parts. - * The default is two parts, ie. stereo. - * - * @author Phil Burk (C) 2016 Mobileer Inc - * @see Circuit - * @see PassThrough - */ -public class MultiPassThrough extends UnitGenerator implements UnitSink, UnitSource { - public UnitInputPort input; - public UnitOutputPort output; - private final int mNumParts; - - /* Define Unit Ports used by connect() and set(). */ - public MultiPassThrough(int numParts) { - mNumParts = numParts; - addPort(input = new UnitInputPort(numParts, "Input")); - addPort(output = new UnitOutputPort(numParts, "Output")); - } - - public MultiPassThrough() { - this(2); // stereo - } - - @Override - public UnitInputPort getInput() { - return input; - } - - @Override - public UnitOutputPort getOutput() { - return output; - } - - @Override - public void generate(int start, int limit) { - for (int partIndex = 0; partIndex < mNumParts; partIndex++) { - double[] inputs = input.getValues(partIndex); - double[] outputs = output.getValues(partIndex); - - for (int i = start; i < limit; i++) { - outputs[i] = inputs[i]; - } - } - } -} diff --git a/src/com/jsyn/unitgen/Multiply.java b/src/com/jsyn/unitgen/Multiply.java deleted file mode 100644 index ded7646..0000000 --- a/src/com/jsyn/unitgen/Multiply.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * 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.unitgen; - -import com.jsyn.ports.UnitInputPort; -import com.jsyn.ports.UnitOutputPort; - -/** - * This unit multiplies its two inputs.
- * - *
- * output = inputA * inputB
- * 
- * - *
- * Note that some units have an amplitude port, which controls an internal multiply. So you may not - * need this unit. - * - * @author Phil Burk (C) 2009 Mobileer Inc - * @version 016 - * @see MultiplyAdd - * @see Subtract - */ -public class Multiply extends UnitBinaryOperator { - public Multiply() { - } - - /** Connect a to inputA and b to inputB. */ - public Multiply(UnitOutputPort a, UnitOutputPort b) { - a.connect(inputA); - b.connect(inputB); - } - - /** Connect a to inputA and b to inputB and connect output to c. */ - public Multiply(UnitOutputPort a, UnitOutputPort b, UnitInputPort c) { - this(a, b); - output.connect(c); - } - - @Override - public void generate(int start, int limit) { - double[] aValues = inputA.getValues(); - double[] bValues = inputB.getValues(); - double[] outputs = output.getValues(); - for (int i = start; i < limit; i++) { - outputs[i] = aValues[i] * bValues[i]; - } - } - -} diff --git a/src/com/jsyn/unitgen/MultiplyAdd.java b/src/com/jsyn/unitgen/MultiplyAdd.java deleted file mode 100644 index adbee6c..0000000 --- a/src/com/jsyn/unitgen/MultiplyAdd.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * 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.unitgen; - -import com.jsyn.ports.UnitInputPort; -import com.jsyn.ports.UnitOutputPort; - -/** - *
- * output = (inputA * inputB) + inputC
- * 
- * - * @author Phil Burk (C) 2009 Mobileer Inc - * @version 016 - * @see Multiply - * @see Add - */ -public class MultiplyAdd extends UnitGenerator { - public UnitInputPort inputA; - public UnitInputPort inputB; - public UnitInputPort inputC; - public UnitOutputPort output; - - /* Define Unit Ports used by connect() and set(). */ - public MultiplyAdd() { - addPort(inputA = new UnitInputPort("InputA")); - addPort(inputB = new UnitInputPort("InputB")); - addPort(inputC = new UnitInputPort("InputC")); - addPort(output = new UnitOutputPort("Output")); - } - - @Override - public void generate(int start, int limit) { - double[] aValues = inputA.getValues(); - double[] bValues = inputB.getValues(); - double[] cValues = inputC.getValues(); - double[] outputs = output.getValues(); - for (int i = start; i < limit; i++) { - outputs[i] = (aValues[i] * bValues[i]) + cValues[i]; - } - } - -} diff --git a/src/com/jsyn/unitgen/Pan.java b/src/com/jsyn/unitgen/Pan.java deleted file mode 100644 index bc90984..0000000 --- a/src/com/jsyn/unitgen/Pan.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright 1997 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.unitgen; - -import com.jsyn.ports.UnitInputPort; -import com.jsyn.ports.UnitOutputPort; - -/** - * Pan unit. The profile is constant amplitude and not constant energy. - *

- * Takes an input and pans it between two outputs based on value of pan. When pan is -1, output[0] - * is input, and output[1] is zero. When pan is 0, output[0] and output[1] are both input/2. When - * pan is +1, output[0] is zero, and output[1] is input. - *

- * - * @author (C) 1997 Phil Burk, SoftSynth.com - * @see Select - */ -public class Pan extends UnitGenerator { - public UnitInputPort input; - /** - * Pan control varies from -1.0 for full left to +1.0 for full right. Set to 0.0 for center. - */ - public UnitInputPort pan; - public UnitOutputPort output; - - public Pan() { - addPort(input = new UnitInputPort("Input")); - addPort(pan = new UnitInputPort("Pan")); - addPort(output = new UnitOutputPort(2, "Output")); - } - - @Override - public void generate(int start, int limit) { - double[] inputs = input.getValues(); - double[] panPtr = pan.getValues(); - double[] outputs_0 = output.getValues(0); - double[] outputs_1 = output.getValues(1); - - for (int i = start; i < limit; i++) { - double gainB = (panPtr[i] * 0.5) + 0.5; /* - * Scale and offset to 0.0 to 1.0 - */ - double gainA = 1.0 - gainB; - double inVal = inputs[i]; - outputs_0[i] = inVal * gainA; - outputs_1[i] = inVal * gainB; - } - } -} diff --git a/src/com/jsyn/unitgen/PanControl.java b/src/com/jsyn/unitgen/PanControl.java deleted file mode 100644 index 63bddd8..0000000 --- a/src/com/jsyn/unitgen/PanControl.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 1997 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.unitgen; - -import com.jsyn.ports.UnitInputPort; -import com.jsyn.ports.UnitOutputPort; - -/** - * PanControl unit. - *

- * Generates control signals that can be used to control a mixer or the amplitude ports of two - * units. - * - *

- * temp = (pan * 0.5) + 0.5;
- * output[0] = temp;
- * output[1] = 1.0 - temp;
- * 
- *

- * - * @author (C) 1997-2009 Phil Burk, SoftSynth.com - */ -public class PanControl extends UnitGenerator { - public UnitInputPort pan; - public UnitOutputPort output; - - /* Define Unit Ports used by connect() and set(). */ - public PanControl() { - addPort(pan = new UnitInputPort("Pan")); - addPort(output = new UnitOutputPort(2, "Output", 0.0)); - } - - @Override - public void generate(int start, int limit) { - double[] panPtr = pan.getValues(); - double[] output0s = output.getValues(0); - double[] output1s = output.getValues(1); - - for (int i = start; i < limit; i++) { - double gainB = (panPtr[i] * 0.5) + 0.5; /* - * Scale and offset to 0.0 to 1.0 - */ - output0s[i] = 1.0 - gainB; - output1s[i] = gainB; - } - } -} diff --git a/src/com/jsyn/unitgen/ParabolicEnvelope.java b/src/com/jsyn/unitgen/ParabolicEnvelope.java deleted file mode 100644 index 6de97d9..0000000 --- a/src/com/jsyn/unitgen/ParabolicEnvelope.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright 1997 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.unitgen; - -import com.jsyn.ports.UnitInputPort; -import com.jsyn.ports.UnitOutputPort; - -/** - * ParabolicEnvelope unit. Output goes from zero to amplitude then back to zero in a parabolic arc. - *

- * Generate a short parabolic envelope that could be used for granular synthesis. The output starts - * at zero, peaks at the value of amplitude then returns to zero. This unit has two states, IDLE and - * RUNNING. If a trigger is received when IDLE, the envelope is started and another trigger is sent - * out the triggerOutput port. This triggerOutput can be used to latch values for the synthesis of a - * grain. If a trigger is received when RUNNING, then it is ignored and passed out the triggerPass - * port. The triggerPass can be connected to the triggerInput of another ParabolicEnvelope. Thus you - * can implement a simple grain allocation scheme by daisy chaining the triggers of - * ParabolicEnvelopes. - *

- * The envelope is generated by a double integrator method so it uses relatively little CPU time. - * - * @author (C) 1997 Phil Burk, SoftSynth.com - * @see EnvelopeDAHDSR - */ -public class ParabolicEnvelope extends UnitGenerator { - - /** Fastest repeat rate of envelope if it were continually retriggered in Hertz. */ - public UnitInputPort frequency; - /** True value triggers envelope when in resting state. */ - public UnitInputPort triggerInput; - public UnitInputPort amplitude; - - /** Trigger output when envelope started. */ - public UnitOutputPort triggerOutput; - /** Input trigger passed out if ignored for daisy chaining. */ - public UnitOutputPort triggerPass; - public UnitOutputPort output; - - private double slope; - private double curve; - private double level; - private boolean running; - - /* Define Unit Ports used by connect() and set(). */ - public ParabolicEnvelope() { - addPort(triggerInput = new UnitInputPort("Input")); - addPort(frequency = new UnitInputPort("Frequency", UnitOscillator.DEFAULT_FREQUENCY)); - addPort(amplitude = new UnitInputPort("Amplitude", UnitOscillator.DEFAULT_AMPLITUDE)); - - addPort(output = new UnitOutputPort("Output")); - addPort(triggerOutput = new UnitOutputPort("TriggerOutput")); - addPort(triggerPass = new UnitOutputPort("TriggerPass")); - } - - @Override - public void generate(int start, int limit) { - double[] frequencies = frequency.getValues(); - double[] amplitudes = amplitude.getValues(); - double[] triggerInputs = triggerInput.getValues(); - double[] outputs = output.getValues(); - double[] triggerPasses = triggerPass.getValues(); - double[] triggerOutputs = triggerOutput.getValues(); - - for (int i = start; i < limit; i++) { - if (!running) { - if (triggerInputs[i] > 0) { - double freq = frequencies[i] * synthesisEngine.getInverseNyquist(); - freq = (freq > 1.0) ? 1.0 : ((freq < -1.0) ? -1.0 : freq); - double ampl = amplitudes[i]; - double freq2 = freq * freq; /* Square frequency. */ - slope = 4.0 * ampl * (freq - freq2); - curve = -8.0 * ampl * freq2; - level = 0.0; - triggerOutputs[i] = UnitGenerator.TRUE; - running = true; - } else { - triggerOutputs[i] = UnitGenerator.FALSE; - } - triggerPasses[i] = UnitGenerator.FALSE; - } else /* RUNNING */ - { - level += slope; - slope += curve; - if (level <= 0.0) { - level = 0.0; - running = false; - /* Autostop? - FIXME */ - } - - triggerOutputs[i] = UnitGenerator.FALSE; - triggerPasses[i] = triggerInputs[i]; - } - outputs[i] = level; - } - } -} diff --git a/src/com/jsyn/unitgen/PassThrough.java b/src/com/jsyn/unitgen/PassThrough.java deleted file mode 100644 index 8ac0b93..0000000 --- a/src/com/jsyn/unitgen/PassThrough.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright 2011 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.unitgen; - -/** - * Pass the input through to the output unchanged. This is often used for distributing a signal to - * multiple ports inside a circuit. It can also be used as a summing node, in other words, a mixer. - * - * @author Phil Burk (C) 2011 Mobileer Inc - * @see Circuit - * @see MultiPassThrough - */ -public class PassThrough extends UnitFilter { - @Override - public void generate(int start, int limit) { - double[] inputs = input.getValues(); - double[] outputs = output.getValues(); - - for (int i = start; i < limit; i++) { - outputs[i] = inputs[i]; - } - - } -} diff --git a/src/com/jsyn/unitgen/PeakFollower.java b/src/com/jsyn/unitgen/PeakFollower.java deleted file mode 100644 index 7bf0508..0000000 --- a/src/com/jsyn/unitgen/PeakFollower.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright 1997 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.unitgen; - -import com.jsyn.ports.UnitInputPort; -import com.jsyn.ports.UnitOutputPort; -import com.jsyn.ports.UnitVariablePort; - -/** - * Tracks the peaks of an input signal. This can be used to monitor the overall amplitude of a - * signal. The output can be used to drive color organs, vocoders, VUmeters, etc. Output drops - * exponentially when the input drops below the current output level. The output approaches zero - * based on the value on the halfLife port. - * - * @author (C) 1997-2009 Phil Burk, SoftSynth.com - */ -public class PeakFollower extends UnitGenerator { - public UnitInputPort input; - public UnitVariablePort current; - public UnitInputPort halfLife; - public UnitOutputPort output; - - private double previousHalfLife = -1.0; - private double decayScalar = 0.99; - - /* Define Unit Ports used by connect() and set(). */ - public PeakFollower() { - addPort(input = new UnitInputPort("Input")); - addPort(halfLife = new UnitInputPort(1, "HalfLife", 0.1)); - addPort(current = new UnitVariablePort("Current")); - addPort(output = new UnitOutputPort("Output")); - } - - @Override - public void generate(int start, int limit) { - double[] inputs = input.getValues(); - double[] outputs = output.getValues(); - double currentHalfLife = halfLife.getValues()[0]; - double currentValue = current.getValue(); - - if (currentHalfLife != previousHalfLife) { - decayScalar = this.convertHalfLifeToMultiplier(currentHalfLife); - previousHalfLife = currentHalfLife; - } - - double scalar = 1.0 - decayScalar; - - for (int i = start; i < limit; i++) { - double inputValue = inputs[i]; - if (inputValue < 0.0) { - inputValue = -inputValue; // absolute value - } - - if (inputValue >= currentValue) { - currentValue = inputValue; - } else { - currentValue = currentValue * scalar; - } - - outputs[i] = currentValue; - } - - /* - * When current gets close to zero, set current to zero to prevent FP underflow, which can - * cause a severe performance degradation in 'C'. - */ - if (currentValue < VERY_SMALL_FLOAT) { - currentValue = 0.0; - } - - current.setValue(currentValue); - } -} diff --git a/src/com/jsyn/unitgen/PhaseShifter.java b/src/com/jsyn/unitgen/PhaseShifter.java deleted file mode 100644 index 4b17245..0000000 --- a/src/com/jsyn/unitgen/PhaseShifter.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright 2014 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.unitgen; - -import com.jsyn.ports.UnitInputPort; - -/** - * PhaseShifter effects processor. This unit emulates a common guitar pedal effect but without the - * LFO modulation. You can use your own modulation source connected to the "offset" port. Different - * frequencies are phase shifted varying amounts using a series of AllPass filters. By feeding the - * output back to the input we can get varying phase cancellation. This implementation was based on - * code posted to the music-dsp archive by Ross Bencina. http://www.musicdsp.org/files/phaser.cpp - * - * @author (C) 2014 Phil Burk, Mobileer Inc - * @see FilterLowPass - * @see FilterAllPass - * @see RangeConverter - */ - -public class PhaseShifter extends UnitFilter { - /** - * Connect an oscillator to this port to sweep the phase. A range of 0.05 to 0.4 is a good - * start. - */ - public UnitInputPort offset; - public UnitInputPort feedback; - public UnitInputPort depth; - - private double zm1; - private double[] xs; - private double[] ys; - - public PhaseShifter() { - this(6); - } - - public PhaseShifter(int numStages) { - addPort(offset = new UnitInputPort("Offset", 0.1)); - addPort(feedback = new UnitInputPort("Feedback", 0.7)); - addPort(depth = new UnitInputPort("Depth", 1.0)); - - xs = new double[numStages]; - ys = new double[numStages]; - } - - @Override - public void generate(int start, int limit) { - double[] inputs = input.getValues(); - double[] outputs = output.getValues(); - double[] feedbacks = feedback.getValues(); - double[] depths = depth.getValues(); - double[] offsets = offset.getValues(); - double gain; - - for (int i = start; i < limit; i++) { - // Support audio rate modulation. - double currentOffset = offsets[i]; - - // Prevent gain from exceeding 1.0. - gain = 1.0 - (currentOffset * currentOffset); - if (gain < -1.0) { - gain = -1.0; - } - - double x = inputs[i] + (zm1 * feedbacks[i]); - // Cascaded all-pass filters. - for (int stage = 0; stage < xs.length; stage++) { - double temp = ys[stage] = (gain * (ys[stage] - x)) + xs[stage]; - xs[stage] = x; - x = temp; - } - zm1 = x; - outputs[i] = inputs[i] + (x * depths[i]); - } - } -} diff --git a/src/com/jsyn/unitgen/PinkNoise.java b/src/com/jsyn/unitgen/PinkNoise.java deleted file mode 100644 index 84aa2f2..0000000 --- a/src/com/jsyn/unitgen/PinkNoise.java +++ /dev/null @@ -1,128 +0,0 @@ -/* - * 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.unitgen; - -import com.jsyn.ports.UnitInputPort; -import com.jsyn.ports.UnitOutputPort; -import com.jsyn.util.PseudoRandom; - -/** - * Random output with 3dB per octave rolloff providing a soft natural noise sound. Generated using - * Gardner method. Optimization suggested by James McCartney uses a tree to select which random - * value to replace. - * - *

- *  x x x x x x x x x x x x x x x x 
- *  x   x   x   x   x   x   x   x   
- *  x       x       x       x       
- *  x               x               
- *  x
- * 
- * - * Tree is generated by counting trailing zeros in an increasing index. When the index is zero, no - * random number is selected. Author: Phil Burk (C) 1996 SoftSynth.com. - */ - -public class PinkNoise extends UnitGenerator implements UnitSource { - - public UnitInputPort amplitude; - public UnitOutputPort output; - - private final int NUM_ROWS = 16; - private final int RANDOM_BITS = 24; - private final int RANDOM_SHIFT = 32 - RANDOM_BITS; - - private PseudoRandom randomNum; - protected double prevNoise, currNoise; - - private long[] rows = new long[NUM_ROWS]; // NEXT RANDOM UNSIGNED 32 - private double scalar; // used to scale within range of -1.0 to +1.0 - private int runningSum; // used to optimize summing of generators - private int index; // incremented with each sample - private int indexMask; // index wrapped and ANDing with this mask - - /* Define Unit Ports used by connect() and set(). */ - public PinkNoise() { - addPort(amplitude = new UnitInputPort("Amplitude", UnitOscillator.DEFAULT_AMPLITUDE)); - addPort(output = new UnitOutputPort("Output")); - - randomNum = new PseudoRandom(); - - // set up for N rows of generators - index = 0; - indexMask = (1 << NUM_ROWS) - 1; - - // Calculate maximum possible signed random value. Extra 1 for white - // noise always added. - int pmax = (NUM_ROWS + 1) * (1 << (RANDOM_BITS - 1)); - scalar = 1.0 / pmax; - - // initialize rows - for (int i = 0; i < NUM_ROWS; i++) { - rows[i] = 0; - } - - runningSum = 0; - } - - @Override - public void generate(int start, int limit) { - double[] amplitudes = amplitude.getValues(); - double[] outputs = output.getValues(); - - for (int i = start; i < limit; i++) { - outputs[i] = generatePinkNoise() * amplitudes[i]; - } - } - - public double generatePinkNoise() { - index = (index + 1) & indexMask; - - // If index is zero, don't update any random values. - if (index != 0) { - // Determine how many trailing zeros in PinkIndex. - // This algorithm will hang of n==0 so test first - int numZeros = 0; - int n = index; - - while ((n & 1) == 0) { - n = n >> 1; - numZeros++; - } - - // Replace the indexed ROWS random value. - // Subtract and add back to RunningSum instead of adding all the - // random values together. Only one changes each time. - runningSum -= rows[numZeros]; - int newRandom = randomNum.nextRandomInteger() >> RANDOM_SHIFT; - runningSum += newRandom; - rows[numZeros] = newRandom; - } - - // Add extra white noise value. - int newRandom = randomNum.nextRandomInteger() >> RANDOM_SHIFT; - int sum = runningSum + newRandom; - - // Scale to range of -1.0 to 0.9999. - return scalar * sum; - } - - @Override - public UnitOutputPort getOutput() { - return output; - } -} diff --git a/src/com/jsyn/unitgen/PitchDetector.java b/src/com/jsyn/unitgen/PitchDetector.java deleted file mode 100644 index da6a0e3..0000000 --- a/src/com/jsyn/unitgen/PitchDetector.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright 2012 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.unitgen; - -import com.jsyn.ports.UnitInputPort; -import com.jsyn.ports.UnitOutputPort; -import com.jsyn.util.AutoCorrelator; -import com.jsyn.util.SignalCorrelator; - -/** - * Estimate the fundamental frequency of a monophonic signal. Analyzes an input signal and outputs - * an estimated period in frames and a frequency in Hertz. The frequency is frameRate/period. The - * confidence tells you how accurate the estimate is. When the confidence is low, you should ignore - * the period. You can use a CompareUnit and a LatchUnit to hold values that you are confident of. - *

- * Note that a stable monophonic signal is required for accurate pitch tracking. - * - * @author (C) 2012 Phil Burk, Mobileer Inc - */ -public class PitchDetector extends UnitGenerator { - public UnitInputPort input; - - public UnitOutputPort period; - public UnitOutputPort confidence; - public UnitOutputPort frequency; - public UnitOutputPort updated; - - protected SignalCorrelator signalCorrelator; - - private double lastFrequency = 440.0; - private double lastPeriod = 44100.0 / lastFrequency; // result of analysis TODO update for 48000 - private double lastConfidence = 0.0; // Measure of confidence in the result. - - private static final int LOWEST_FREQUENCY = 40; - private static final int HIGHEST_RATE = 48000; - private static final int CYCLES_NEEDED = 2; - - public PitchDetector() { - super(); - addPort(input = new UnitInputPort("Input")); - - addPort(period = new UnitOutputPort("Period")); - addPort(confidence = new UnitOutputPort("Confidence")); - addPort(frequency = new UnitOutputPort("Frequency")); - addPort(updated = new UnitOutputPort("Updated")); - signalCorrelator = createSignalCorrelator(); - } - - public SignalCorrelator createSignalCorrelator() { - int framesNeeded = HIGHEST_RATE * CYCLES_NEEDED / LOWEST_FREQUENCY; - return new AutoCorrelator(framesNeeded); - } - - @Override - public void generate(int start, int limit) { - double[] inputs = input.getValues(); - double[] periods = period.getValues(); - double[] confidences = confidence.getValues(); - double[] frequencies = frequency.getValues(); - double[] updateds = updated.getValues(); - - for (int i = start; i < limit; i++) { - double current = inputs[i]; - if (signalCorrelator.addSample(current)) { - lastPeriod = signalCorrelator.getPeriod(); - if (lastPeriod < 0.1) { - System.out.println("ILLEGAL PERIOD"); - } - double currentFrequency = getFrameRate() / (lastPeriod + 0); - double confidence = signalCorrelator.getConfidence(); - if (confidence > 0.1) { - if (true) { - double coefficient = confidence * 0.2; - // Take weighted average with previous frequency. - lastFrequency = ((lastFrequency * (1.0 - coefficient)) + (currentFrequency * coefficient)); - } else { - lastFrequency = ((lastFrequency * lastConfidence) + (currentFrequency * confidence)) - / (lastConfidence + confidence); - } - } - lastConfidence = confidence; - updateds[i] = 1.0; - } else { - updateds[i] = 0.0; - } - periods[i] = lastPeriod; - confidences[i] = lastConfidence; - frequencies[i] = lastFrequency; - } - } - - /** - * For debugging only. - * - * @return internal array of correlation results. - */ - public float[] getDiffs() { - return signalCorrelator.getDiffs(); - } - -} diff --git a/src/com/jsyn/unitgen/PitchToFrequency.java b/src/com/jsyn/unitgen/PitchToFrequency.java deleted file mode 100644 index 9086749..0000000 --- a/src/com/jsyn/unitgen/PitchToFrequency.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.jsyn.unitgen; - -import com.softsynth.math.AudioMath; - -public class PitchToFrequency extends PowerOfTwo { - - public PitchToFrequency() { - input.setup(0.0, 60.0, 127.0); - } - - /** - * Convert from MIDI pitch to an octave offset from Concert A. - */ - @Override - public double adjustInput(double in) { - return (in - AudioMath.CONCERT_A_PITCH) * (1.0/12.0); - } - - /** - * Convert scaler to a frequency relative to Concert A. - */ - @Override - public double adjustOutput(double out) { - return out * AudioMath.getConcertAFrequency(); - } -} diff --git a/src/com/jsyn/unitgen/PowerOfTwo.java b/src/com/jsyn/unitgen/PowerOfTwo.java deleted file mode 100644 index 5916860..0000000 --- a/src/com/jsyn/unitgen/PowerOfTwo.java +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright 2010 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.unitgen; - -import com.jsyn.ports.UnitInputPort; -import com.jsyn.ports.UnitOutputPort; - -/** - * output = (2.0^input) This is useful for converting a pitch modulation value into a frequency - * scaler. An input value of +1.0 will output 2.0 for an octave increase. An input value of -1.0 - * will output 0.5 for an octave decrease. - * - * This implementation uses a table lookup to optimize for - * speed. It is accurate enough for tuning. It also checks to see if the current input value is the - * same as the previous input value. If so then it reuses the previous computed value. - * - * @author Phil Burk (C) 2010 Mobileer Inc - */ -public class PowerOfTwo extends UnitGenerator { - /** - * Offset in octaves. - */ - public UnitInputPort input; - public UnitOutputPort output; - - private static double[] table; - private static final int NUM_VALUES = 2048; - // Cached computation. - private double lastInput = 0.0; - private double lastOutput = 1.0; - - static { - // Add guard point for faster interpolation. - // Add another point to handle inputs like -1.5308084989341915E-17, - // which generate indices above range. - table = new double[NUM_VALUES + 2]; - // Fill one octave of the table. - for (int i = 0; i < table.length; i++) { - double value = Math.pow(2.0, ((double) i) / NUM_VALUES); - table[i] = value; - } - } - - public PowerOfTwo() { - addPort(input = new UnitInputPort("Input")); - input.setup(-8.0, 0.0, 8.0); - addPort(output = new UnitOutputPort("Output")); - } - - @Override - public void generate(int start, int limit) { - double[] inputs = input.getValues(); - double[] outputs = output.getValues(); - - for (int i = start; i < limit; i++) { - double in = inputs[i]; - // Can we reuse a previously computed value? - if (in == lastInput) { - outputs[i] = lastOutput; - } else { - lastInput = in; - double adjustedInput = adjustInput(in); - int octave = (int) Math.floor(adjustedInput); - double normal = adjustedInput - octave; - // Do table lookup. - double findex = normal * NUM_VALUES; - int index = (int) findex; - double fraction = findex - index; - double value = table[index] + (fraction * (table[index + 1] - table[index])); - - // Adjust for octave. - while (octave > 0) { - octave -= 1; - value *= 2.0; - } - while (octave < 0) { - octave += 1; - value *= 0.5; - } - double adjustedOutput = adjustOutput(value); - outputs[i] = adjustedOutput; - lastOutput = adjustedOutput; - } - } - } - - public double adjustInput(double in) { - return in; - } - - public double adjustOutput(double out) { - return out; - } -} diff --git a/src/com/jsyn/unitgen/PulseOscillator.java b/src/com/jsyn/unitgen/PulseOscillator.java deleted file mode 100644 index 5ac7352..0000000 --- a/src/com/jsyn/unitgen/PulseOscillator.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * 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.unitgen; - -import com.jsyn.ports.UnitInputPort; - -/** - * Simple pulse wave oscillator. - * - * @author Phil Burk (C) 2009 Mobileer Inc - */ -public class PulseOscillator extends UnitOscillator { - /** - * Pulse width varies from -1.0 to +1.0. At 0.0 the pulse is actually a square wave. - */ - public UnitInputPort width; - - public PulseOscillator() { - addPort(width = new UnitInputPort("Width")); - } - - @Override - public void generate(int start, int limit) { - double[] frequencies = frequency.getValues(); - double[] amplitudes = amplitude.getValues(); - double[] widths = width.getValues(); - double[] outputs = output.getValues(); - - // Variables have a single value. - double currentPhase = phase.getValue(); - - for (int i = start; i < limit; i++) { - // Generate sawtooth phaser to provide phase for pulse generation. - double phaseIncrement = convertFrequencyToPhaseIncrement(frequencies[i]); - currentPhase = incrementWrapPhase(currentPhase, phaseIncrement); - double ampl = amplitudes[i]; - // Either full negative or positive amplitude. - outputs[i] = (currentPhase < widths[i]) ? -ampl : ampl; - } - - // Value needs to be saved for next time. - phase.setValue(currentPhase); - } - -} diff --git a/src/com/jsyn/unitgen/PulseOscillatorBL.java b/src/com/jsyn/unitgen/PulseOscillatorBL.java deleted file mode 100644 index c0e234c..0000000 --- a/src/com/jsyn/unitgen/PulseOscillatorBL.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * 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.unitgen; - -import com.jsyn.engine.MultiTable; -import com.jsyn.ports.UnitInputPort; - -/** - * Pulse oscillator that uses two band limited sawtooth waveforms. - * - * @author Phil Burk (C) 2009 Mobileer Inc - */ -public class PulseOscillatorBL extends SawtoothOscillatorBL { - /** Controls the duty cycle of the pulse waveform. - * The width varies from -1.0 to +1.0. - * When width is zero the output is a square wave. - */ - public UnitInputPort width; - - public PulseOscillatorBL() { - addPort(width = new UnitInputPort("Width")); - } - - @Override - protected double generateBL(MultiTable multiTable, double currentPhase, - double positivePhaseIncrement, double flevel, int i) { - double[] widths = width.getValues(); - double width = widths[i]; - width = (width > 0.999) ? 0.999 : ((width < -0.999) ? -0.999 : width); - - double val1 = multiTable.calculateSawtooth(currentPhase, positivePhaseIncrement, flevel); - - // Generate second sawtooth so we can add them together. - double phase2 = currentPhase + 1.0 - width; // 180 degrees out of phase - if (phase2 >= 1.0) { - phase2 -= 2.0; - } - double val2 = multiTable.calculateSawtooth(phase2, positivePhaseIncrement, flevel); - - /* - * Need to adjust amplitude based on positive phaseInc and width. little less than half at - * Nyquist/2.0! - */ - double scale = 1.0 - positivePhaseIncrement; - return scale * (val1 - val2 - width); - } -} diff --git a/src/com/jsyn/unitgen/RaisedCosineEnvelope.java b/src/com/jsyn/unitgen/RaisedCosineEnvelope.java deleted file mode 100644 index c32417c..0000000 --- a/src/com/jsyn/unitgen/RaisedCosineEnvelope.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright 2011 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.unitgen; - -/** - * An envelope that can be used in a GrainFarm to shape the amplitude of a Grain. The envelope - * starts at 0.0, rises to 1.0, then returns to 0.0 following a cosine curve. - * - *

- * output = 0.5 - (0.5 * cos(phase))
- * 
- * - * @author Phil Burk (C) 2011 Mobileer Inc - * @see GrainFarm - */ -public class RaisedCosineEnvelope extends GrainCommon implements GrainEnvelope { - protected double phase; - protected double phaseIncrement; - - public RaisedCosineEnvelope() { - setFrameRate(44100); - setDuration(0.1); - } - - /** - * @return next value of the envelope. - */ - @Override - public double next() { - phase += phaseIncrement; - if (phase > (2.0 * Math.PI)) { - return 0.0; - } else { - return 0.5 - (0.5 * Math.cos(phase)); // TODO optimize using Taylor expansion - } - } - - /** - * @return true if there are more envelope values left. - */ - @Override - public boolean hasMoreValues() { - return (phase < (2.0 * Math.PI)); - } - - /** - * Reset the envelope back to the beginning. - */ - @Override - public void reset() { - phase = 0.0; - } - - @Override - public void setDuration(double duration) { - phaseIncrement = 2.0 * Math.PI / (getFrameRate() * duration); - } - -} diff --git a/src/com/jsyn/unitgen/RangeConverter.java b/src/com/jsyn/unitgen/RangeConverter.java deleted file mode 100644 index ae94b0f..0000000 --- a/src/com/jsyn/unitgen/RangeConverter.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2014 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.unitgen; - -import com.jsyn.ports.UnitInputPort; - -/** - * Convert an input signal between -1.0 and +1.0 to the range min to max. This is handy when using - * an oscillator as a modulator. - * - * @author (C) 2014 Phil Burk, Mobileer Inc - * @see EdgeDetector - */ -public class RangeConverter extends UnitFilter { - public UnitInputPort min; - public UnitInputPort max; - - /* Define Unit Ports used by connect() and set(). */ - public RangeConverter() { - addPort(min = new UnitInputPort("Min", 40.0)); - addPort(max = new UnitInputPort("Max", 2000.0)); - } - - @Override - public void generate(int start, int limit) { - double[] inputs = input.getValues(); - double[] mins = min.getValues(); - double[] maxs = max.getValues(); - double[] outputs = output.getValues(); - - for (int i = start; i < limit; i++) { - double low = mins[i]; - outputs[i] = low + ((maxs[i] - low) * (inputs[i] + 1) * 0.5); - } - } -} diff --git a/src/com/jsyn/unitgen/RectangularWindow.java b/src/com/jsyn/unitgen/RectangularWindow.java deleted file mode 100644 index d61f763..0000000 --- a/src/com/jsyn/unitgen/RectangularWindow.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2013 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.unitgen; - -import com.jsyn.data.SpectralWindow; - -/** - * Window that is just 1.0. Flat like a rectangle. - * - * @author Phil Burk (C) 2013 Mobileer Inc - * @see SpectralFFT - */ -public class RectangularWindow implements SpectralWindow { - static RectangularWindow instance = new RectangularWindow(); - - @Override - /** This always returns 1.0. Do not pass indices outside the window range. */ - public double get(int index) { - return 1.0; // impressive, eh? - } - - public static RectangularWindow getInstance() { - return instance; - } -} diff --git a/src/com/jsyn/unitgen/RedNoise.java b/src/com/jsyn/unitgen/RedNoise.java deleted file mode 100644 index d3e4321..0000000 --- a/src/com/jsyn/unitgen/RedNoise.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 1997 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.unitgen; - -import com.jsyn.util.PseudoRandom; - -/** - * RedNoise unit. This unit interpolates straight line segments between pseudo-random numbers to - * produce "red" noise. It is a grittier alternative to the white generator WhiteNoise. It is also - * useful as a slowly changing random control generator for natural sounds. Frequency port controls - * the number of times per second that a new random number is chosen. - * - * @author (C) 1997 Phil Burk, SoftSynth.com - * @see WhiteNoise - */ -public class RedNoise extends UnitOscillator { - private PseudoRandom randomNum; - protected double prevNoise, currNoise; - - /* Define Unit Ports used by connect() and set(). */ - public RedNoise() { - super(); - randomNum = new PseudoRandom(); - } - - @Override - public void generate(int start, int limit) { - double[] amplitudes = amplitude.getValues(); - double[] frequencies = frequency.getValues(); - double[] outputs = output.getValues(); - double currPhase = phase.getValue(); - double phaseIncrement, currOutput; - - double framePeriod = getFramePeriod(); - - for (int i = start; i < limit; i++) { - // compute phase - phaseIncrement = frequencies[i] * framePeriod; - - // verify that phase is within minimums and is not negative - if (phaseIncrement < 0.0) { - phaseIncrement = 0.0 - phaseIncrement; - } - if (phaseIncrement > 1.0) { - phaseIncrement = 1.0; - } - - currPhase += phaseIncrement; - - // calculate new random whenever phase passes 1.0 - if (currPhase > 1.0) { - prevNoise = currNoise; - currNoise = randomNum.nextRandomDouble(); - // reset phase for interpolation - currPhase -= 1.0; - } - - // interpolate current - currOutput = prevNoise + (currPhase * (currNoise - prevNoise)); - outputs[i] = currOutput * amplitudes[i]; - } - - // store new phase - phase.setValue(currPhase); - } -} diff --git a/src/com/jsyn/unitgen/SampleGrainFarm.java b/src/com/jsyn/unitgen/SampleGrainFarm.java deleted file mode 100644 index 3f908d6..0000000 --- a/src/com/jsyn/unitgen/SampleGrainFarm.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2011 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.unitgen; - -import com.jsyn.data.FloatSample; -import com.jsyn.ports.UnitInputPort; - -/** - * A GrainFarm that uses a FloatSample as source material. In this example we load a FloatSample for - * use as a source material. - * - *

-	synth.add(sampleGrainFarm = new SampleGrainFarm());
-	// Load a sample that we want to "granulate" from a file.
-	sample = SampleLoader.loadFloatSample(sampleFile);
-	sampleGrainFarm.setSample(sample);
-	// Use a ramp to move smoothly within the file.
-	synth.add(ramp = new ContinuousRamp());
-	ramp.output.connect(sampleGrainFarm.position);
-
- * - * @author Phil Burk (C) 2011 Mobileer Inc - */ -public class SampleGrainFarm extends GrainFarm { - private FloatSample sample; - public UnitInputPort position; - public UnitInputPort positionRange; - - public SampleGrainFarm() { - super(); - addPort(position = new UnitInputPort("Position", 0.0)); - addPort(positionRange = new UnitInputPort("PositionRange", 0.0)); - } - - @Override - public void allocate(int numGrains) { - Grain[] grainArray = new Grain[numGrains]; - for (int i = 0; i < numGrains; i++) { - Grain grain = new Grain(new SampleGrainSource(), new RaisedCosineEnvelope()); - grainArray[i] = grain; - } - setGrainArray(grainArray); - } - - @Override - public void setupGrain(Grain grain, int i) { - SampleGrainSource sampleGrainSource = (SampleGrainSource) grain.getSource(); - sampleGrainSource.setSample(sample); - sampleGrainSource.setPosition(position.getValues()[i]); - sampleGrainSource.setPositionRange(positionRange.getValues()[i]); - super.setupGrain(grain, i); - } - - public void setSample(FloatSample sample) { - this.sample = sample; - } -} diff --git a/src/com/jsyn/unitgen/SampleGrainSource.java b/src/com/jsyn/unitgen/SampleGrainSource.java deleted file mode 100644 index f33817f..0000000 --- a/src/com/jsyn/unitgen/SampleGrainSource.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * 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.unitgen; - -import com.jsyn.data.FloatSample; - -public class SampleGrainSource extends GrainCommon implements GrainSource { - private FloatSample sample; - private double position; // ranges from -1.0 to 1.0 - private double positionRange; - private double phase; // ranges from 0.0 to 1.0 - private double phaseIncrement; - private int numFramesGuarded; - private static final double MAX_PHASE = 0.9999999999; - - @Override - public double next() { - phase += phaseIncrement; - if (phase > MAX_PHASE) { - phase = MAX_PHASE; - } - double fractionalIndex = phase * numFramesGuarded; - return sample.interpolate(fractionalIndex); - } - - @Override - public void setRate(double rate) { - phaseIncrement = rate * sample.getFrameRate() / (getFrameRate() * numFramesGuarded); - } - - public void setSample(FloatSample sample) { - this.sample = sample; - numFramesGuarded = sample.getNumFrames() - 1; - } - - public void setPosition(double position) { - this.position = position; - } - - @Override - public void reset() { - double randomPosition = position + (positionRange * (Math.random() - 0.5)); - phase = (randomPosition * 0.5) + 0.5; - if (phase < 0.0) { - phase = 0.0; - } else if (phase > MAX_PHASE) { - phase = MAX_PHASE; - } - } - - public void setPositionRange(double positionRange) { - this.positionRange = positionRange; - } - -} diff --git a/src/com/jsyn/unitgen/SawtoothOscillator.java b/src/com/jsyn/unitgen/SawtoothOscillator.java deleted file mode 100644 index 1b3dead..0000000 --- a/src/com/jsyn/unitgen/SawtoothOscillator.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * 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.unitgen; - -/** - * Simple sawtooth oscillator. - * - * @author Phil Burk (C) 2009 Mobileer Inc - */ -public class SawtoothOscillator extends UnitOscillator { - - @Override - public void generate(int start, int limit) { - - double[] frequencies = frequency.getValues(); - double[] amplitudes = amplitude.getValues(); - double[] outputs = output.getValues(); - - // Variables have a single value. - double currentPhase = phase.getValue(); - - for (int i = start; i < limit; i++) { - /* Generate sawtooth phasor to provide phase for sine generation. */ - double phaseIncrement = convertFrequencyToPhaseIncrement(frequencies[i]); - currentPhase = incrementWrapPhase(currentPhase, phaseIncrement); - outputs[i] = currentPhase * amplitudes[i]; - } - - // Value needs to be saved for next time. - phase.setValue(currentPhase); - } - -} diff --git a/src/com/jsyn/unitgen/SawtoothOscillatorBL.java b/src/com/jsyn/unitgen/SawtoothOscillatorBL.java deleted file mode 100644 index 8b58f6c..0000000 --- a/src/com/jsyn/unitgen/SawtoothOscillatorBL.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * 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.unitgen; - -import com.jsyn.engine.MultiTable; - -/** - * Sawtooth oscillator that uses multiple wave tables for band limiting. This requires more CPU than - * a plain SawtoothOscillator but has less aliasing at high frequencies. - * - * @author Phil Burk (C) 2009 Mobileer Inc - */ -public class SawtoothOscillatorBL extends UnitOscillator { - @Override - public void generate(int start, int limit) { - MultiTable multiTable = MultiTable.getInstance(); - - double[] frequencies = frequency.getValues(); - double[] amplitudes = amplitude.getValues(); - double[] outputs = output.getValues(); - // Variables have a single value. - double currentPhase = phase.getValue(); - - double phaseIncrement = convertFrequencyToPhaseIncrement(frequencies[0]); - double positivePhaseIncrement = Math.abs(phaseIncrement); - // This is very expensive so we moved it outside the loop. - // Try to optimize it with a table lookup. - double flevel = multiTable.convertPhaseIncrementToLevel(positivePhaseIncrement); - - for (int i = start; i < limit; i++) { - /* Generate sawtooth phasor to provide phase for sine generation. */ - phaseIncrement = convertFrequencyToPhaseIncrement(frequencies[i]); - currentPhase = incrementWrapPhase(currentPhase, phaseIncrement); - positivePhaseIncrement = Math.abs(phaseIncrement); - - double val = generateBL(multiTable, currentPhase, positivePhaseIncrement, flevel, i); - - outputs[i] = val * amplitudes[i]; - } - - // Value needs to be saved for next time. - phase.setValue(currentPhase); - } - - protected double generateBL(MultiTable multiTable, double currentPhase, - double positivePhaseIncrement, double flevel, int i) { - /* Calculate table level then use it for lookup. */ - return multiTable.calculateSawtooth(currentPhase, positivePhaseIncrement, flevel); - } - -} diff --git a/src/com/jsyn/unitgen/SawtoothOscillatorDPW.java b/src/com/jsyn/unitgen/SawtoothOscillatorDPW.java deleted file mode 100644 index 27d0c5a..0000000 --- a/src/com/jsyn/unitgen/SawtoothOscillatorDPW.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * 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.unitgen; - -/** - * Sawtooth DPW oscillator (a sawtooth with reduced aliasing). - * Based on a paper by Antti Huovilainen and Vesa Valimaki: - * http://www.scribd.com/doc/33863143/New-Approaches-to-Digital-Subtractive-Synthesis - * - * @author Phil Burk and Lisa Tolentino (C) 2009 Mobileer Inc - */ -public class SawtoothOscillatorDPW extends UnitOscillator { - // At a very low frequency, switch from DPW to raw sawtooth. - private static final double VERY_LOW_FREQUENCY = 2.0 * 0.1 / 44100.0; - private double z1; - private double z2; - - @Override - public void generate(int start, int limit) { - double[] frequencies = frequency.getValues(); - double[] amplitudes = amplitude.getValues(); - double[] outputs = output.getValues(); - - // Variables have a single value. - double currentPhase = phase.getValue(); - - for (int i = start; i < limit; i++) { - /* Generate raw sawtooth phaser. */ - double phaseIncrement = convertFrequencyToPhaseIncrement(frequencies[i]); - currentPhase = incrementWrapPhase(currentPhase, phaseIncrement); - - /* Square the raw sawtooth. */ - double squared = currentPhase * currentPhase; - // Differentiate using a delayed value. - double diffed = squared - z2; - z2 = z1; - z1 = squared; - - /* Calculate scaling based on phaseIncrement */ - double pinc = phaseIncrement; - // Absolute value. - if (pinc < 0.0) { - pinc = 0.0 - pinc; - } - - double dpw; - // If the frequency is very low then just use the raw sawtooth. - // This avoids divide by zero problems and scaling problems. - if (pinc < VERY_LOW_FREQUENCY) { - dpw = currentPhase; - } else { - dpw = diffed * 0.25 / pinc; - } - - outputs[i] = amplitudes[i] * dpw; - } - - // Value needs to be saved for next time. - phase.setValue(currentPhase); - } - -} diff --git a/src/com/jsyn/unitgen/SchmidtTrigger.java b/src/com/jsyn/unitgen/SchmidtTrigger.java deleted file mode 100644 index 64129ff..0000000 --- a/src/com/jsyn/unitgen/SchmidtTrigger.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * 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.unitgen; - -import com.jsyn.ports.UnitInputPort; -import com.jsyn.ports.UnitOutputPort; - -/** - * SchmidtTrigger unit. - *

- * Output logic level value with hysteresis. Transition high when input exceeds setLevel. Only go - * low when input is below resetLevel. This can be used to reject low level noise on the input - * signal. The default values for setLevel and resetLevel are both 0.0. Setting setLevel to 0.1 and - * resetLevel to -0.1 will give some hysteresis. The outputPulse is a single sample wide pulse set - * when the output transitions from low to high. - * - *

- * if (output == 0.0)
- *     output = (input > setLevel) ? 1.0 : 0.0;
- * else if (output > 0.0)
- *     output = (input <= resetLevel) ? 0.0 : 1.0;
- * else
- *     output = previous_output;
- * 
- * - * @author Phil Burk (C) 2009 Mobileer Inc - * @see Compare - */ -public class SchmidtTrigger extends UnitFilter { - public UnitInputPort setLevel; - public UnitInputPort resetLevel; - public UnitOutputPort outputPulse; - - /* Define Unit Ports used by connect() and set(). */ - public SchmidtTrigger() { - addPort(setLevel = new UnitInputPort("SetLevel")); - addPort(resetLevel = new UnitInputPort("ResetLevel")); - addPort(input = new UnitInputPort("Input")); - addPort(outputPulse = new UnitOutputPort("OutputPulse")); - } - - @Override - public void generate(int start, int limit) { - double[] inPtr = input.getValues(); - double[] pulsePtr = outputPulse.getValues(); - double[] outPtr = output.getValues(); - double[] setPtr = setLevel.getValues(); - double[] resetPtr = resetLevel.getValues(); - - double outputValue = outPtr[0]; - boolean state = (outputValue > UnitGenerator.FALSE); - for (int i = start; i < limit; i++) { - pulsePtr[i] = UnitGenerator.FALSE; - if (state) { - if (inPtr[i] <= resetPtr[i]) { - state = false; - outputValue = UnitGenerator.FALSE; - } - } else { - if (inPtr[i] > setPtr[i]) { - state = true; - outputValue = UnitGenerator.TRUE; - pulsePtr[i] = UnitGenerator.TRUE; /* Single impulse. */ - } - } - outPtr[i] = outputValue; - } - } -} diff --git a/src/com/jsyn/unitgen/Select.java b/src/com/jsyn/unitgen/Select.java deleted file mode 100644 index 6d8792e..0000000 --- a/src/com/jsyn/unitgen/Select.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2004 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.unitgen; - -import com.jsyn.ports.UnitInputPort; -import com.jsyn.ports.UnitOutputPort; - -/** - * SelectUnit unit. Select InputA or InputB based on value on Select port. - * - *
 
-   output = ( select > 0.0 ) ? inputB : inputA;
- 
- * - * @author (C) 2004-2009 Phil Burk, SoftSynth.com - */ - -public class Select extends UnitGenerator { - public UnitInputPort inputA; - public UnitInputPort inputB; - public UnitInputPort select; - public UnitOutputPort output; - - public Select() { - addPort(inputA = new UnitInputPort("InputA")); - addPort(inputB = new UnitInputPort("InputB")); - addPort(select = new UnitInputPort("Select")); - addPort(output = new UnitOutputPort("Output")); - } - - @Override - public void generate(int start, int limit) { - double[] inputAs = inputA.getValues(); - double[] inputBs = inputB.getValues(); - double[] selects = select.getValues(); - double[] outputs = output.getValues(); - - for (int i = start; i < limit; i++) { - outputs[i] = (selects[i] > UnitGenerator.FALSE) ? inputBs[i] : inputAs[i]; - } - } -} diff --git a/src/com/jsyn/unitgen/SequentialDataReader.java b/src/com/jsyn/unitgen/SequentialDataReader.java deleted file mode 100644 index 901767b..0000000 --- a/src/com/jsyn/unitgen/SequentialDataReader.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * 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.unitgen; - -import com.jsyn.ports.UnitDataQueuePort; -import com.jsyn.ports.UnitInputPort; -import com.jsyn.ports.UnitOutputPort; - -/** - * Base class for reading a sample or envelope. - * - * @author Phil Burk (C) 2009 Mobileer Inc - */ -public abstract class SequentialDataReader extends UnitGenerator { - public UnitDataQueuePort dataQueue; - public UnitInputPort amplitude; - public UnitOutputPort output; - - /* Define Unit Ports used by connect() and set(). */ - public SequentialDataReader() { - addPort(dataQueue = new UnitDataQueuePort("Data")); - addPort(amplitude = new UnitInputPort("Amplitude", UnitOscillator.DEFAULT_AMPLITUDE)); - } -} diff --git a/src/com/jsyn/unitgen/SequentialDataWriter.java b/src/com/jsyn/unitgen/SequentialDataWriter.java deleted file mode 100644 index cb3bb11..0000000 --- a/src/com/jsyn/unitgen/SequentialDataWriter.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * 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.unitgen; - -import com.jsyn.ports.UnitDataQueuePort; -import com.jsyn.ports.UnitInputPort; - -/** - * Base class for writing to a sample. - * - * Note that you must call start() on subclasses of this unit because it does not have an output for pulling data. - * - * @author Phil Burk (C) 2009 Mobileer Inc - */ -public abstract class SequentialDataWriter extends UnitGenerator { - public UnitDataQueuePort dataQueue; - public UnitInputPort input; - - public SequentialDataWriter() { - addPort(dataQueue = new UnitDataQueuePort("Data")); - } - - /** - * This unit won't do anything unless you start() it. - */ - @Override - public boolean isStartRequired() { - return true; - } -} diff --git a/src/com/jsyn/unitgen/SineOscillator.java b/src/com/jsyn/unitgen/SineOscillator.java deleted file mode 100644 index 8c49ead..0000000 --- a/src/com/jsyn/unitgen/SineOscillator.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * 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.unitgen; - -/** - * Sine oscillator generates a frequency controlled sine wave. It is implemented using a fast Taylor - * expansion. - * - * @author Phil Burk (C) 2009 Mobileer Inc - */ -public class SineOscillator extends UnitOscillator { - public SineOscillator() { - } - - public SineOscillator(double freq) { - frequency.set(freq); - } - - public SineOscillator(double freq, double amp) { - frequency.set(freq); - amplitude.set(amp); - } - - @Override - public void generate(int start, int limit) { - double[] frequencies = frequency.getValues(); - double[] amplitudes = amplitude.getValues(); - double[] outputs = output.getValues(); - double currentPhase = phase.getValue(); - - for (int i = start; i < limit; i++) { - /* Generate sawtooth phasor to provide phase for sine generation. */ - double phaseIncrement = convertFrequencyToPhaseIncrement(frequencies[i]); - currentPhase = incrementWrapPhase(currentPhase, phaseIncrement); - if (true) { - double value = fastSin(currentPhase); - outputs[i] = value * amplitudes[i]; - } else { - // Slower but more accurate implementation. - outputs[i] = Math.sin(currentPhase * Math.PI) * amplitudes[i]; - } - } - - phase.setValue(currentPhase); - } - - /** - * Calculate sine using Taylor expansion. Do not use values outside the range. - * - * @param currentPhase in the range of -1.0 to +1.0 for one cycle - */ - public static double fastSin(double currentPhase) { - // Factorial constants so code is easier to read. - final double IF3 = 1.0 / (2 * 3); - final double IF5 = IF3 / (4 * 5); - final double IF7 = IF5 / (6 * 7); - final double IF9 = IF7 / (8 * 9); - final double IF11 = IF9 / (10 * 11); - - /* Wrap phase back into region where results are more accurate. */ - double yp = (currentPhase > 0.5) ? 1.0 - currentPhase : ((currentPhase < (-0.5)) ? (-1.0) - - currentPhase : currentPhase); - - double x = yp * Math.PI; - double x2 = (x * x); - /* Taylor expansion out to x**11/11! factored into multiply-adds */ - double fastsin = x - * (x2 * (x2 * (x2 * (x2 * ((x2 * (-IF11)) + IF9) - IF7) + IF5) - IF3) + 1); - return fastsin; - } -} diff --git a/src/com/jsyn/unitgen/SineOscillatorPhaseModulated.java b/src/com/jsyn/unitgen/SineOscillatorPhaseModulated.java deleted file mode 100644 index 7631dff..0000000 --- a/src/com/jsyn/unitgen/SineOscillatorPhaseModulated.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * 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.unitgen; - -import com.jsyn.ports.UnitInputPort; - -/** - * Sine oscillator with a phase modulation input. Phase modulation is similar to frequency - * modulation but is easier to use in some ways. - * - *
- * output = sin(PI * (phase + modulation))
- * 
- * - * @author Phil Burk (C) 2009 Mobileer Inc - */ -public class SineOscillatorPhaseModulated extends SineOscillator { - public UnitInputPort modulation; - - /* Define Unit Ports used by connect() and set(). */ - public SineOscillatorPhaseModulated() { - super(); - addPort(modulation = new UnitInputPort("Modulation")); - } - - @Override - public void generate(int start, int limit) { - double[] frequencies = frequency.getValues(); - double[] amplitudes = amplitude.getValues(); - double[] outputs = output.getValues(); - double[] modulations = modulation.getValues(); - double currentPhase = phase.getValue(); - - for (int i = start; i < limit; i++) { - /* Generate sawtooth phaser to provide phase for sine generation. */ - double phaseIncrement = convertFrequencyToPhaseIncrement(frequencies[i]); - currentPhase = incrementWrapPhase(currentPhase, phaseIncrement); - double modulatedPhase = currentPhase + modulations[i]; - double value; - if (false) { - // TODO Compare benchmarks. - while (modulatedPhase >= 1.0) { - modulatedPhase -= 2.0; - } - while (modulatedPhase < -1.0) { - modulatedPhase += 2.0; - } - value = fastSin(modulatedPhase); - } else { - value = Math.sin(modulatedPhase * Math.PI); - } - outputs[i] = value * amplitudes[i]; - // System.out.format("Sine: freq = %10.4f , amp = %8.5f, out = %8.5f, phase = %8.5f, frame = %8d\n", - // frequencies[i], amplitudes[i],outputs[i],currentPhase,frame++ ); - } - - phase.setValue(currentPhase); - } - -} diff --git a/src/com/jsyn/unitgen/SpectralFFT.java b/src/com/jsyn/unitgen/SpectralFFT.java deleted file mode 100644 index f3e881a..0000000 --- a/src/com/jsyn/unitgen/SpectralFFT.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Copyright 2013 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.unitgen; - -import java.util.Arrays; - -import com.jsyn.data.SpectralWindow; -import com.jsyn.data.Spectrum; -import com.jsyn.ports.UnitInputPort; -import com.jsyn.ports.UnitSpectralOutputPort; -import com.softsynth.math.FourierMath; - -/** - * Periodically transform the input signal using an FFT. Output complete spectra. - * - * @author Phil Burk (C) 2013 Mobileer Inc - * @version 016 - * @see SpectralIFFT - * @see Spectrum - * @see SpectralFilter - */ -public class SpectralFFT extends UnitGenerator { - public UnitInputPort input; - /** - * Provides complete complex spectra when the FFT completes. - */ - public UnitSpectralOutputPort output; - private double[] buffer; - private int cursor; - private SpectralWindow window = RectangularWindow.getInstance(); - private int sizeLog2; - private int offset; - private boolean running; - - /* Define Unit Ports used by connect() and set(). */ - public SpectralFFT() { - this(Spectrum.DEFAULT_SIZE_LOG_2); - } - - /** - * @param sizeLog2 for example, pass 10 to get a 1024 bin FFT - */ - public SpectralFFT(int sizeLog2) { - addPort(input = new UnitInputPort("Input")); - addPort(output = new UnitSpectralOutputPort("Output", 1 << sizeLog2)); - setSizeLog2(sizeLog2); - } - - /** - * Please do not change the size of the FFT while JSyn is running. - * - * @param sizeLog2 for example, pass 9 to get a 512 bin FFT - */ - public void setSizeLog2(int sizeLog2) { - this.sizeLog2 = sizeLog2; - output.setSize(1 << sizeLog2); - buffer = output.getSpectrum().getReal(); - cursor = 0; - } - - public int getSizeLog2() { - return sizeLog2; - } - - @Override - public void generate(int start, int limit) { - if (!running) { - int mask = (1 << sizeLog2) - 1; - if (((getSynthesisEngine().getFrameCount() - offset) & mask) == 0) { - running = true; - cursor = 0; - } - } - // Don't use "else" because "running" may have changed in above block. - if (running) { - double[] inputs = input.getValues(); - for (int i = start; i < limit; i++) { - buffer[cursor] = inputs[i] * window.get(cursor); - ++cursor; - // When it is full, do the FFT. - if (cursor == buffer.length) { - Spectrum spectrum = output.getSpectrum(); - Arrays.fill(spectrum.getImaginary(), 0.0); - FourierMath.fft(buffer.length, spectrum.getReal(), spectrum.getImaginary()); - output.advance(); - cursor = 0; - } - } - } - } - - public SpectralWindow getWindow() { - return window; - } - - /** - * Multiply input data by this window before doing the FFT. The default is a RectangularWindow. - */ - public void setWindow(SpectralWindow window) { - this.window = window; - } - - /** - * The FFT will be performed on a frame that is a multiple of the size plus this offset. - * - * @param offset - */ - public void setOffset(int offset) { - this.offset = offset; - } - - public int getOffset() { - return offset; - } - -} diff --git a/src/com/jsyn/unitgen/SpectralFilter.java b/src/com/jsyn/unitgen/SpectralFilter.java deleted file mode 100644 index 758c8e7..0000000 --- a/src/com/jsyn/unitgen/SpectralFilter.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Copyright 2014 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.unitgen; - -import com.jsyn.data.SpectralWindow; -import com.jsyn.data.SpectralWindowFactory; -import com.jsyn.data.Spectrum; -import com.jsyn.ports.UnitInputPort; -import com.jsyn.ports.UnitOutputPort; -import com.jsyn.ports.UnitSpectralInputPort; -import com.jsyn.ports.UnitSpectralOutputPort; - -/** - * Process a signal using multiple overlapping FFT and IFFT pairs. For passthrough, you can connect - * the spectral outputs to the spectral inputs. Or you can connect one or more SpectralProcessors - * between them. - * - *
- * for (int i = 0; i < numFFTs; i++) {
- *     filter.getSpectralOutput(i).connect(processors[i].input);
- *     processors[i].output.connect(filter.getSpectralInput(i));
- * }
- * 
- * - * See the example program "HearSpectralFilter.java". Note that this spectral API is experimental - * and may change at any time. - * - * @author Phil Burk (C) 2014 Mobileer Inc - * @see SpectralProcessor - */ -public class SpectralFilter extends Circuit implements UnitSink, UnitSource { - public UnitInputPort input; - public UnitOutputPort output; - - private SpectralFFT[] ffts; - private SpectralIFFT[] iffts; - private PassThrough inlet; // fan out to FFTs - private PassThrough sum; // mix output of IFFTs - - /** - * Create a default sized filter with 2 FFT/IFFT pairs and a sizeLog2 of - * Spectrum.DEFAULT_SIZE_LOG_2. - */ - public SpectralFilter() { - this(2, Spectrum.DEFAULT_SIZE_LOG_2); - } - - /** - * @param numFFTs number of FFT/IFFT pairs for the overlap and add - * @param sizeLog2 for example, use 10 to get a 1024 bin FFT, 12 for 4096 - */ - public SpectralFilter(int numFFTs, int sizeLog2) { - add(inlet = new PassThrough()); - add(sum = new PassThrough()); - ffts = new SpectralFFT[numFFTs]; - iffts = new SpectralIFFT[numFFTs]; - int offset = (1 << sizeLog2) / numFFTs; - for (int i = 0; i < numFFTs; i++) { - add(ffts[i] = new SpectralFFT(sizeLog2)); - inlet.output.connect(ffts[i].input); - ffts[i].setOffset(i * offset); - - add(iffts[i] = new SpectralIFFT()); - iffts[i].output.connect(sum.input); - } - setWindow(SpectralWindowFactory.getHammingWindow(sizeLog2)); - - addPort(input = inlet.input); - addPort(output = sum.output); - } - - public SpectralWindow getWindow() { - return ffts[0].getWindow(); - } - - /** - * Specify one window to be used for all FFTs and IFFTs. The window should be the same size as - * the FFTs. - * - * @param window default is HammingWindow - * @see SpectralWindowFactory - */ - public void setWindow(SpectralWindow window) { - // Use the same window everywhere. - for (int i = 0; i < ffts.length; i++) { - ffts[i].setWindow(window); // TODO review, both sides or just one - iffts[i].setWindow(window); - } - } - - @Override - public UnitOutputPort getOutput() { - return output; - } - - @Override - public UnitInputPort getInput() { - return input; - } - - /** - * @param i - * @return the output of the indexed FFT - */ - public UnitSpectralOutputPort getSpectralOutput(int i) { - return ffts[i].output; - } - - /** - * @param i - * @return the input of the indexed IFFT - */ - public UnitSpectralInputPort getSpectralInput(int i) { - return iffts[i].input; - } -} diff --git a/src/com/jsyn/unitgen/SpectralIFFT.java b/src/com/jsyn/unitgen/SpectralIFFT.java deleted file mode 100644 index c040e52..0000000 --- a/src/com/jsyn/unitgen/SpectralIFFT.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright 2013 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.unitgen; - -import com.jsyn.data.SpectralWindow; -import com.jsyn.data.Spectrum; -import com.jsyn.ports.UnitOutputPort; -import com.jsyn.ports.UnitSpectralInputPort; -import com.softsynth.math.FourierMath; - -/** - * Periodically transform the input signal using an Inverse FFT. - * - * @author Phil Burk (C) 2013 Mobileer Inc - * @version 016 - * @see SpectralFFT - */ -public class SpectralIFFT extends UnitGenerator { - public UnitSpectralInputPort input; - public UnitOutputPort output; - private Spectrum localSpectrum; - private double[] buffer; - private int cursor; - private SpectralWindow window = RectangularWindow.getInstance(); - - /* Define Unit Ports used by connect() and set(). */ - public SpectralIFFT() { - addPort(output = new UnitOutputPort()); - addPort(input = new UnitSpectralInputPort("Input")); - } - - @Override - public void generate(int start, int limit) { - double[] outputs = output.getValues(); - - if (buffer == null) { - if (input.isAvailable()) { - Spectrum spectrum = input.getSpectrum(); - int size = spectrum.size(); - localSpectrum = new Spectrum(size); - buffer = localSpectrum.getReal(); - cursor = 0; - } else { - for (int i = start; i < limit; i++) { - outputs[i] = 0.0; - } - } - } - - if (buffer != null) { - for (int i = start; i < limit; i++) { - if (cursor == 0) { - Spectrum spectrum = input.getSpectrum(); - spectrum.copyTo(localSpectrum); - FourierMath.ifft(buffer.length, localSpectrum.getReal(), - localSpectrum.getImaginary()); - } - - outputs[i] = buffer[cursor] * window.get(cursor); - cursor += 1; - if (cursor == buffer.length) { - cursor = 0; - } - } - } - } - - public SpectralWindow getWindow() { - return window; - } - - /** - * Multiply output data by this window after doing the FFT. The default is a RectangularWindow. - */ - public void setWindow(SpectralWindow window) { - this.window = window; - } -} diff --git a/src/com/jsyn/unitgen/SpectralProcessor.java b/src/com/jsyn/unitgen/SpectralProcessor.java deleted file mode 100644 index de96877..0000000 --- a/src/com/jsyn/unitgen/SpectralProcessor.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright 2014 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.unitgen; - -import com.jsyn.data.Spectrum; -import com.jsyn.ports.UnitSpectralInputPort; -import com.jsyn.ports.UnitSpectralOutputPort; - -/** - * This is a base class for implementing your own spectral processing units. You need to implement - * the processSpectrum() method. - * - * @author Phil Burk (C) 2014 Mobileer Inc - * @see Spectrum - */ -public abstract class SpectralProcessor extends UnitGenerator { - public UnitSpectralInputPort input; - public UnitSpectralOutputPort output; - private int counter; - - /* Define Unit Ports used by connect() and set(). */ - public SpectralProcessor() { - addPort(output = new UnitSpectralOutputPort()); - addPort(input = new UnitSpectralInputPort()); - } - - /* Define Unit Ports used by connect() and set(). */ - public SpectralProcessor(int size) { - addPort(output = new UnitSpectralOutputPort(size)); - addPort(input = new UnitSpectralInputPort()); - } - - @Override - public void generate(int start, int limit) { - for (int i = start; i < limit; i++) { - if (counter == 0) { - if (input.isAvailable()) { - Spectrum inputSpectrum = input.getSpectrum(); - Spectrum outputSpectrum = output.getSpectrum(); - processSpectrum(inputSpectrum, outputSpectrum); - - output.advance(); - counter = inputSpectrum.size() - 1; - } - } else { - counter--; - } - } - } - - /** - * Define this method to implement your own processor. - * - * @param inputSpectrum - * @param outputSpectrum - */ - public abstract void processSpectrum(Spectrum inputSpectrum, Spectrum outputSpectrum); - -} diff --git a/src/com/jsyn/unitgen/SquareOscillator.java b/src/com/jsyn/unitgen/SquareOscillator.java deleted file mode 100644 index aaca2d0..0000000 --- a/src/com/jsyn/unitgen/SquareOscillator.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * 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.unitgen; - -/** - * Simple square wave oscillator. - * - * @author Phil Burk (C) 2009 Mobileer Inc - */ -public class SquareOscillator extends UnitOscillator { - - @Override - public void generate(int start, int limit) { - double[] frequencies = frequency.getValues(); - double[] amplitudes = amplitude.getValues(); - double[] outputs = output.getValues(); - - // Variables have a single value. - double currentPhase = phase.getValue(); - - for (int i = start; i < limit; i++) { - /* Generate sawtooth phasor to provide phase for square generation. */ - double phaseIncrement = convertFrequencyToPhaseIncrement(frequencies[i]); - currentPhase = incrementWrapPhase(currentPhase, phaseIncrement); - - double ampl = amplitudes[i]; - // Either full negative or positive amplitude. - outputs[i] = (currentPhase < 0.0) ? -ampl : ampl; - } - - // Value needs to be saved for next time. - phase.setValue(currentPhase); - } - -} diff --git a/src/com/jsyn/unitgen/SquareOscillatorBL.java b/src/com/jsyn/unitgen/SquareOscillatorBL.java deleted file mode 100644 index cb9e141..0000000 --- a/src/com/jsyn/unitgen/SquareOscillatorBL.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * 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.unitgen; - -import com.jsyn.engine.MultiTable; - -/** - * Band-limited square wave oscillator. This requires more CPU than a SquareOscillator but is less - * noisy at high frequencies. - * - * @author Phil Burk (C) 2009 Mobileer Inc - */ -public class SquareOscillatorBL extends SawtoothOscillatorBL { - @Override - protected double generateBL(MultiTable multiTable, double currentPhase, - double positivePhaseIncrement, double flevel, int i) { - double val1 = multiTable.calculateSawtooth(currentPhase, positivePhaseIncrement, flevel); - - /* Generate second sawtooth so we can add them together. */ - double phase2 = currentPhase + 1.0; /* 180 degrees out of phase. */ - if (phase2 >= 1.0) { - phase2 -= 2.0; - } - double val2 = multiTable.calculateSawtooth(phase2, positivePhaseIncrement, flevel); - - /* - * Need to adjust amplitude based on positive phaseInc. little less than half at - * Nyquist/2.0! - */ - final double STARTAMP = 0.92; /* Derived by viewing waveforms with TJ_SEEOSC */ - double scale = STARTAMP - positivePhaseIncrement; - return scale * (val1 - val2); - } -} diff --git a/src/com/jsyn/unitgen/StereoStreamWriter.java b/src/com/jsyn/unitgen/StereoStreamWriter.java deleted file mode 100644 index b387836..0000000 --- a/src/com/jsyn/unitgen/StereoStreamWriter.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * 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.unitgen; - -import java.io.IOException; - -import com.jsyn.io.AudioOutputStream; -import com.jsyn.ports.UnitInputPort; - -/** - * Write two samples per audio frame to an AudioOutputStream as interleaved samples. - * - * Note that you must call start() on this unit because it does not have an output for pulling data. - * - * @author Phil Burk (C) 2009 Mobileer Inc - */ -public class StereoStreamWriter extends UnitStreamWriter { - public StereoStreamWriter() { - addPort(input = new UnitInputPort(2, "Input")); - } - - @Override - public void generate(int start, int limit) { - double[] leftInputs = input.getValues(0); - double[] rightInputs = input.getValues(1); - AudioOutputStream output = outputStream; - if (output != null) { - try { - for (int i = start; i < limit; i++) { - output.write(leftInputs[i]); - output.write(rightInputs[i]); - } - } catch (IOException e) { - e.printStackTrace(); - output = null; - } - } - } -} diff --git a/src/com/jsyn/unitgen/StochasticGrainScheduler.java b/src/com/jsyn/unitgen/StochasticGrainScheduler.java deleted file mode 100644 index 1f79877..0000000 --- a/src/com/jsyn/unitgen/StochasticGrainScheduler.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2011 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.unitgen; - -import com.jsyn.util.PseudoRandom; - -/** - * Use a random function to schedule grains. - * - * @author Phil Burk (C) 2011 Mobileer Inc - */ -public class StochasticGrainScheduler implements GrainScheduler { - PseudoRandom pseudoRandom = new PseudoRandom(); - - @Override - public double nextDuration(double duration) { - return duration; - } - - @Override - public double nextGap(double duration, double density) { - if (density < 0.00000001) { - density = 0.00000001; - } - double gapRange = duration * (1.0 - density) / density; - return pseudoRandom.random() * gapRange; - } - -} diff --git a/src/com/jsyn/unitgen/Subtract.java b/src/com/jsyn/unitgen/Subtract.java deleted file mode 100644 index d9ca035..0000000 --- a/src/com/jsyn/unitgen/Subtract.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * 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.unitgen; - -/** - * This unit performs a signed subtraction on its two inputs. - * - *
- * output = inputA - inputB
- * 
- * - * @author Phil Burk (C) 2009 Mobileer Inc - * @version 016 - * @see MultiplyAdd - * @see Subtract - */ -public class Subtract extends UnitBinaryOperator { - @Override - public void generate(int start, int limit) { - double[] aValues = inputA.getValues(); - double[] bValues = inputB.getValues(); - double[] outputs = output.getValues(); - - for (int i = start; i < limit; i++) { - outputs[i] = aValues[i] - bValues[i]; - } - } -} diff --git a/src/com/jsyn/unitgen/TriangleOscillator.java b/src/com/jsyn/unitgen/TriangleOscillator.java deleted file mode 100644 index ada2d6e..0000000 --- a/src/com/jsyn/unitgen/TriangleOscillator.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * 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.unitgen; - -/** - * Simple triangle wave oscillator. - * - * @author Phil Burk (C) 2009 Mobileer Inc - */ -public class TriangleOscillator extends UnitOscillator { - int frame; - - public TriangleOscillator() { - super(); - phase.setValue(-0.5); - } - - @Override - public void generate(int start, int limit) { - - double[] frequencies = frequency.getValues(); - double[] amplitudes = amplitude.getValues(); - double[] outputs = output.getValues(); - - // Variables have a single value. - double currentPhase = phase.getValue(); - - for (int i = start; i < limit; i++) { - /* Generate sawtooth phasor to provide phase for triangle generation. */ - double phaseIncrement = convertFrequencyToPhaseIncrement(frequencies[i]); - currentPhase = incrementWrapPhase(currentPhase, phaseIncrement); - - /* Map phase to triangle waveform. */ - /* 0 - 0.999 => 0.5-p => +0.5 - -0.5 */ - /* -1.0 - 0.0 => 0.5+p => -0.5 - +0.5 */ - double triangle = (currentPhase >= 0.0) ? (0.5 - currentPhase) : (0.5 + currentPhase); - - outputs[i] = triangle * 2.0 * amplitudes[i]; - } - - // Value needs to be saved for next time. - phase.setValue(currentPhase); - } - -} diff --git a/src/com/jsyn/unitgen/TunableFilter.java b/src/com/jsyn/unitgen/TunableFilter.java deleted file mode 100644 index d2c9f66..0000000 --- a/src/com/jsyn/unitgen/TunableFilter.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * 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. - */ -/** - * Aug 26, 2009 - * com.jsyn.engine.units.TunableFilter.java - */ - -package com.jsyn.unitgen; - -import com.jsyn.ports.UnitInputPort; - -/** - * A UnitFilter with a frequency port. - * - * @author Phil Burk (C) 2009 Mobileer Inc Translated from 'C' to Java by Lisa - * Tolentino. - */ -public abstract class TunableFilter extends UnitFilter { - - static final double DEFAULT_FREQUENCY = 400; - public UnitInputPort frequency; - - public TunableFilter() { - addPort(frequency = new UnitInputPort("Frequency")); - frequency.setup(40.0, DEFAULT_FREQUENCY, 6000.0); - } - -} diff --git a/src/com/jsyn/unitgen/TwoInDualOut.java b/src/com/jsyn/unitgen/TwoInDualOut.java deleted file mode 100644 index a8fea48..0000000 --- a/src/com/jsyn/unitgen/TwoInDualOut.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2004 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.unitgen; - -import com.jsyn.ports.UnitInputPort; -import com.jsyn.ports.UnitOutputPort; - -/** - * This unit combines two discrete inputs into a dual (stereo) output. - * - *
- *  output[0] = inputA
- *  output[1] = inputB
- * 
- * - * @author (C) 2004-2009 Phil Burk, SoftSynth.com - */ - -public class TwoInDualOut extends UnitGenerator { - public UnitInputPort inputA; - public UnitInputPort inputB; - public UnitOutputPort output; - - public TwoInDualOut() { - addPort(inputA = new UnitInputPort("InputA")); - addPort(inputB = new UnitInputPort("InputB")); - addPort(output = new UnitOutputPort(2, "OutputB")); - } - - @Override - public void generate(int start, int limit) { - double[] inputAs = inputA.getValues(); - double[] inputBs = inputB.getValues(); - double[] output0s = output.getValues(0); - double[] output1s = output.getValues(1); - - for (int i = start; i < limit; i++) { - output0s[i] = inputAs[i]; - output1s[i] = inputBs[i]; - } - } -} diff --git a/src/com/jsyn/unitgen/UnitBinaryOperator.java b/src/com/jsyn/unitgen/UnitBinaryOperator.java deleted file mode 100644 index c5675ff..0000000 --- a/src/com/jsyn/unitgen/UnitBinaryOperator.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2010 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.unitgen; - -import com.jsyn.ports.UnitInputPort; -import com.jsyn.ports.UnitOutputPort; - -/** - * Base class for binary arithmetic operators like Add and Compare. - * - * @author Phil Burk (C) 2010 Mobileer Inc - */ -public abstract class UnitBinaryOperator extends UnitGenerator { - public UnitInputPort inputA; - public UnitInputPort inputB; - public UnitOutputPort output; - - /* Define Unit Ports used by connect() and set(). */ - public UnitBinaryOperator() { - addPort(inputA = new UnitInputPort("InputA")); - addPort(inputB = new UnitInputPort("InputB")); - addPort(output = new UnitOutputPort("Output")); - } - - @Override - public abstract void generate(int start, int limit); -} diff --git a/src/com/jsyn/unitgen/UnitFilter.java b/src/com/jsyn/unitgen/UnitFilter.java deleted file mode 100644 index 49976ba..0000000 --- a/src/com/jsyn/unitgen/UnitFilter.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * 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.unitgen; - -import com.jsyn.ports.UnitInputPort; -import com.jsyn.ports.UnitOutputPort; - -/** - * Base class for all filters. - * - * @author Phil Burk (C) 2009 Mobileer Inc - */ -public abstract class UnitFilter extends UnitGenerator implements UnitSink, UnitSource { - public UnitInputPort input; - public UnitOutputPort output; - - /* Define Unit Ports used by connect() and set(). */ - public UnitFilter() { - addPort(input = new UnitInputPort("Input")); - addPort(output = new UnitOutputPort("Output")); - } - - @Override - public UnitInputPort getInput() { - return input; - } - - @Override - public UnitOutputPort getOutput() { - return output; - } - -} diff --git a/src/com/jsyn/unitgen/UnitGate.java b/src/com/jsyn/unitgen/UnitGate.java deleted file mode 100644 index 59144c2..0000000 --- a/src/com/jsyn/unitgen/UnitGate.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2012 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.unitgen; - -import com.jsyn.ports.UnitGatePort; -import com.jsyn.ports.UnitOutputPort; - -/** - * Base class for other envelopes. - * - * @author Phil Burk (C) 2012 Mobileer Inc - */ -public abstract class UnitGate extends UnitGenerator implements UnitSource { - /** - * Input that triggers the envelope. Use amplitude port if you want to connect a signal to be - * modulated by the envelope. - */ - public UnitGatePort input; - public UnitOutputPort output; - - public UnitGate() { - addPort(input = new UnitGatePort("Input")); - addPort(output = new UnitOutputPort("Output")); - } - - @Override - public UnitOutputPort getOutput() { - return output; - } - - /** - * Specify a unit to be disabled when the envelope finishes. - * - * @param unit - */ - public void setupAutoDisable(UnitGenerator unit) { - input.setupAutoDisable(unit); - } - -} diff --git a/src/com/jsyn/unitgen/UnitGenerator.java b/src/com/jsyn/unitgen/UnitGenerator.java deleted file mode 100644 index 1e87ae6..0000000 --- a/src/com/jsyn/unitgen/UnitGenerator.java +++ /dev/null @@ -1,355 +0,0 @@ -/* - * 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.unitgen; - -import java.io.PrintStream; -import java.util.Collection; -import java.util.LinkedHashMap; -import java.util.logging.Logger; - -import com.jsyn.Synthesizer; -import com.jsyn.engine.SynthesisEngine; -import com.jsyn.ports.ConnectableInput; -import com.jsyn.ports.UnitInputPort; -import com.jsyn.ports.UnitOutputPort; -import com.jsyn.ports.UnitPort; -import com.softsynth.shared.time.TimeStamp; - -/** - * Base class for all unit generators. - * - * @author Phil Burk (C) 2009 Mobileer Inc - */ -public abstract class UnitGenerator { - protected static final double VERY_SMALL_FLOAT = 1.0e-26; - - // Some common port names. - public static final String PORT_NAME_INPUT = "Input"; - public static final String PORT_NAME_OUTPUT = "Output"; - public static final String PORT_NAME_PHASE = "Phase"; - public static final String PORT_NAME_FREQUENCY = "Frequency"; - public static final String PORT_NAME_FREQUENCY_SCALER = "FreqScaler"; - public static final String PORT_NAME_AMPLITUDE = "Amplitude"; - public static final String PORT_NAME_PAN = "Pan"; - public static final String PORT_NAME_TIME = "Time"; - public static final String PORT_NAME_CUTOFF = "Cutoff"; - public static final String PORT_NAME_PRESSURE = "Pressure"; - public static final String PORT_NAME_TIMBRE = "Timbre"; - - public static final double FALSE = 0.0; - public static final double TRUE = 1.0; - protected SynthesisEngine synthesisEngine; - private final LinkedHashMap ports = new LinkedHashMap(); - private Circuit circuit; - private long lastFrameCount; - private boolean enabled = true; - private static int nextId; - private final int id = nextId++; - - static Logger logger = Logger.getLogger(UnitGenerator.class.getName()); - - public int getId() { - return id; - } - - public int getFrameRate() { - // return frameRate; - return synthesisEngine.getFrameRate(); - } - - public double getFramePeriod() { - // return framePeriod; // TODO - Why does OldJSynTestSuite fail if I use this! - return synthesisEngine.getFramePeriod(); - } - - public void addPort(UnitPort port) { - port.setUnitGenerator(this); - // Store in a hash table by name. - ports.put(port.getName().toLowerCase(), port); - } - - public void addPort(UnitPort port, String name) { - port.setName(name); - addPort(port); - } - - /** - * Case-insensitive search for a port by name. - * @param portName - * @return matching port or null - */ - public UnitPort getPortByName(String portName) { - return ports.get(portName.toLowerCase()); - } - - public Collection getPorts() { - return ports.values(); - } - - /** - * Perform essential synthesis function. - * - * @param start offset into port buffers - * @param limit limit offset into port buffers for loop - */ - public abstract void generate(int start, int limit); - - /** - * Generate a full block. - */ - public void generate() { - generate(0, Synthesizer.FRAMES_PER_BLOCK); - } - - /** - * @return the synthesisEngine - */ - public SynthesisEngine getSynthesisEngine() { - return synthesisEngine; - } - - /** - * @return the Synthesizer - */ - public Synthesizer getSynthesizer() { - return synthesisEngine; - } - - /** - * @param synthesisEngine the synthesisEngine to set - */ - public void setSynthesisEngine(SynthesisEngine synthesisEngine) { - if ((this.synthesisEngine != null) && (this.synthesisEngine != synthesisEngine)) { - throw new RuntimeException("Unit synthesisEngine already set."); - } - this.synthesisEngine = synthesisEngine; - } - - public UnitGenerator getTopUnit() { - UnitGenerator unit = this; - // Climb to top of circuit hierarchy. - while (unit.circuit != null) { - unit = unit.circuit; - } - logger.fine("getTopUnit " + this + " => " + unit); - return unit; - } - - protected void autoStop() { - synthesisEngine.autoStopUnit(getTopUnit()); - } - - /** Calculate signal based on halflife of an exponential decay. */ - public double convertHalfLifeToMultiplier(double halfLife) { - if (halfLife < (2.0 * getFramePeriod())) { - return 1.0; - } else { - // Oddly enough, this code is valid for both PeakFollower and AsymptoticRamp. - return 1.0 - Math.pow(0.5, 1.0 / (halfLife * getSynthesisEngine().getFrameRate())); - } - } - - protected double incrementWrapPhase(double currentPhase, double phaseIncrement) { - currentPhase += phaseIncrement; - - if (currentPhase >= 1.0) { - currentPhase -= 2.0; - } else if (currentPhase < -1.0) { - currentPhase += 2.0; - } - return currentPhase; - } - - /** Calculate rate based on phase going from 0.0 to 1.0 in time. */ - protected double convertTimeToRate(double time) { - double period2X = synthesisEngine.getInverseNyquist(); - if (time < period2X) { - return 1.0; - } else { - return getFramePeriod() / time; - } - } - - /** Flatten output ports so we don't output a changing signal when stopped. */ - public void flattenOutputs() { - for (UnitPort port : ports.values()) { - if (port instanceof UnitOutputPort) { - ((UnitOutputPort) port).flatten(); - } - } - } - - public void setCircuit(Circuit circuit) { - if ((this.circuit != null) && (circuit != null)) { - throw new RuntimeException("Unit is already in a circuit."); - } - // logger.info( "setCircuit in unit " + this + " with circuit " + circuit ); - this.circuit = circuit; - } - - public Circuit getCircuit() { - return circuit; - } - - public void pullData(long frameCount, int start, int limit) { - // Don't generate twice in case the paths merge. - if (enabled && (frameCount > lastFrameCount)) { - // Do this first to block recursion when there is a feedback loop. - lastFrameCount = frameCount; - // Then pull from all the units that are upstream. - for (UnitPort port : ports.values()) { - if (port instanceof ConnectableInput) { - ((ConnectableInput) port).pullData(frameCount, start, limit); - } - } - // Finally generate using outputs of the upstream units. - generate(start, limit); - } - } - - public boolean isEnabled() { - return enabled; - } - - /** - * If enabled, then a unit will execute if its output is connected to another unit that is - * executed. If not enabled then it will not execute and will not pull data from units that are - * connected to its inputs. Disabling a unit at the output of a tree of units can be used to - * turn off the entire tree, thus saving CPU cycles. - * - * @param enabled - * @see UnitGate#setupAutoDisable(UnitGenerator) - * @see start - */ - public void setEnabled(boolean enabled) { - this.enabled = enabled; - if (!enabled) { - flattenOutputs(); - } - } - - /** - * Some units, for example LineOut and FixedRateMonoWriter, will only work if - * started explicitly. Other units will run when downstream units are started. - * - * @return true if you should call start() for this unit - */ - public boolean isStartRequired() { - return false; - } - - /** - * Start executing this unit directly by adding it to a "run list" of units in the synthesis - * engine. This method is normally only called for the final unit in a chain, for example a - * LineOut. When that final unit executes it will "pull" data from any units connected to its - * inputs. Those units will then pull data their inputs until the entire chain is executed. If - * units are connected in a circle then this will be detected and the infinite recursion will be - * blocked. - * - * @see setEnabled - */ - public void start() { - if (getSynthesisEngine() == null) { - throw new RuntimeException("This " + this.getClass().getName() - + " was not add()ed to a Synthesizer."); - } - getSynthesisEngine().startUnit(this); - } - - /** - * Start a unit at the specified time. - * - * @param time - * @see start - */ - public void start(double time) { - start(new TimeStamp(time)); - } - - /** - * Start a unit at the specified time. - * - * @param timeStamp - * @see start - */ - public void start(TimeStamp timeStamp) { - if (getSynthesisEngine() == null) { - throw new RuntimeException("This " + this.getClass().getName() - + " was not add()ed to a Synthesizer."); - } - getSynthesisEngine().startUnit(this, timeStamp); - } - - /** - * Stop a unit at the specified time. - * - * @param time - * @see start - */ - public void stop(double time) { - stop(new TimeStamp(time)); - } - - public void stop() { - getSynthesisEngine().stopUnit(this); - } - - public void stop(TimeStamp timeStamp) { - getSynthesisEngine().stopUnit(this, timeStamp); - } - - /** - * @deprecated ignored, frameRate comes from the SynthesisEngine - * @param rate - */ - @Deprecated - public void setFrameRate(int rate) { - } - - /** Needed by UnitSink */ - public UnitGenerator getUnitGenerator() { - return this; - } - - /** Needed by UnitVoice */ - public void setPort(String portName, double value, TimeStamp timeStamp) { - UnitInputPort port = (UnitInputPort) getPortByName(portName); - // System.out.println("setPort " + port ); - if (port == null) { - logger.warning("port was null for name " + portName + ", " + this.getClass().getName()); - } else { - port.set(value, timeStamp); - } - } - - public void printConnections() { - printConnections(System.out); - } - - public void printConnections(PrintStream out) { - printConnections(out, 0); - } - - public void printConnections(PrintStream out, int level) { - for (UnitPort port : getPorts()) { - if (port instanceof UnitInputPort) { - ((UnitInputPort) port).printConnections(out, level); - } - } - } - -} diff --git a/src/com/jsyn/unitgen/UnitOscillator.java b/src/com/jsyn/unitgen/UnitOscillator.java deleted file mode 100644 index 5d4c6fa..0000000 --- a/src/com/jsyn/unitgen/UnitOscillator.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * 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.unitgen; - -import com.jsyn.ports.UnitInputPort; -import com.jsyn.ports.UnitOutputPort; -import com.jsyn.ports.UnitVariablePort; -import com.softsynth.shared.time.TimeStamp; - -/** - * Base class for all oscillators. - * - * @author Phil Burk (C) 2009 Mobileer Inc - */ -public abstract class UnitOscillator extends UnitGenerator implements UnitVoice { - /** Frequency in Hertz. */ - public UnitInputPort frequency; - public UnitInputPort amplitude; - public UnitVariablePort phase; - public UnitOutputPort output; - - public static final double DEFAULT_FREQUENCY = 440.0; - public static final double DEFAULT_AMPLITUDE = 1.0; - - /* Define Unit Ports used by connect() and set(). */ - public UnitOscillator() { - addPort(frequency = new UnitInputPort(PORT_NAME_FREQUENCY)); - frequency.setup(40.0, DEFAULT_FREQUENCY, 8000.0); - addPort(amplitude = new UnitInputPort(PORT_NAME_AMPLITUDE, DEFAULT_AMPLITUDE)); - addPort(phase = new UnitVariablePort(PORT_NAME_PHASE)); - addPort(output = new UnitOutputPort(PORT_NAME_OUTPUT)); - } - - /** - * Convert a frequency in Hertz to a phaseIncrement in the range -1.0 to +1.0 - */ - public double convertFrequencyToPhaseIncrement(double freq) { - double phaseIncrement; - try { - phaseIncrement = freq * synthesisEngine.getInverseNyquist(); - } catch (NullPointerException e) { - throw new NullPointerException( - "Null Synth! You probably forgot to add this unit to the Synthesizer!"); - } - // Clip to range. - phaseIncrement = (phaseIncrement > 1.0) ? 1.0 : ((phaseIncrement < -1.0) ? -1.0 - : phaseIncrement); - return phaseIncrement; - } - - @Override - public UnitOutputPort getOutput() { - return output; - } - - public void noteOn(double freq, double ampl) { - frequency.set(freq); - amplitude.set(ampl); - } - - public void noteOff() { - amplitude.set(0.0); - } - - @Override - public void noteOff(TimeStamp timeStamp) { - amplitude.set(0.0, timeStamp); - } - - @Override - public void noteOn(double freq, double ampl, TimeStamp timeStamp) { - frequency.set(freq, timeStamp); - amplitude.set(ampl, timeStamp); - } - - @Override - public void usePreset(int presetIndex) { - } -} diff --git a/src/com/jsyn/unitgen/UnitSink.java b/src/com/jsyn/unitgen/UnitSink.java deleted file mode 100644 index 3e0f55e..0000000 --- a/src/com/jsyn/unitgen/UnitSink.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * 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.unitgen; - -import com.jsyn.ports.UnitInputPort; -import com.softsynth.shared.time.TimeStamp; - -/** - * Interface for unit generators that have an input. - * - * @author Phil Burk, (C) 2009 Mobileer Inc - */ -public interface UnitSink { - public UnitInputPort getInput(); - - /** - * Begin execution of this unit by the Synthesizer. The input will pull data from any output - * port that is connected from it. - */ - public void start(); - - public void start(TimeStamp timeStamp); - - public void stop(); - - public void stop(TimeStamp timeStamp); - - public UnitGenerator getUnitGenerator(); -} diff --git a/src/com/jsyn/unitgen/UnitSource.java b/src/com/jsyn/unitgen/UnitSource.java deleted file mode 100644 index 5ee8134..0000000 --- a/src/com/jsyn/unitgen/UnitSource.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * 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.unitgen; - -import com.jsyn.ports.UnitOutputPort; - -/** - * Interface for things that have that have an output and an associated UnitGenerator. - * - * @author Phil Burk, (C) 2009 Mobileer Inc - */ -public interface UnitSource { - public UnitOutputPort getOutput(); - - public UnitGenerator getUnitGenerator(); -} diff --git a/src/com/jsyn/unitgen/UnitStreamWriter.java b/src/com/jsyn/unitgen/UnitStreamWriter.java deleted file mode 100644 index 0c5bd8b..0000000 --- a/src/com/jsyn/unitgen/UnitStreamWriter.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * 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.unitgen; - -import com.jsyn.io.AudioOutputStream; -import com.jsyn.ports.UnitInputPort; - -/** - * Base class for writing to an AudioOutputStream. - * - * Note that you must call start() on subclasses of this unit because it does not have an output for pulling data. - * - * @author Phil Burk (C) 2009 Mobileer Inc - */ -public abstract class UnitStreamWriter extends UnitGenerator implements UnitSink { - protected AudioOutputStream outputStream; - public UnitInputPort input; - - public AudioOutputStream getOutputStream() { - return outputStream; - } - - public void setOutputStream(AudioOutputStream outputStream) { - this.outputStream = outputStream; - } - - /** - * This unit won't do anything unless you start() it. - */ - @Override - public boolean isStartRequired() { - return true; - } - - @Override - public UnitInputPort getInput() { - return input; - } -} diff --git a/src/com/jsyn/unitgen/UnitVoice.java b/src/com/jsyn/unitgen/UnitVoice.java deleted file mode 100644 index 3f5e6ef..0000000 --- a/src/com/jsyn/unitgen/UnitVoice.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2011 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.unitgen; - -import com.jsyn.util.Instrument; -import com.jsyn.util.VoiceDescription; -import com.softsynth.shared.time.TimeStamp; - -/** - * A voice that can be allocated and played by the VoiceAllocator. - * - * @author Phil Burk (C) 2011 Mobileer Inc - * @see VoiceDescription - * @see Instrument - */ -public interface UnitVoice extends UnitSource { - /** - * Play whatever you consider to be a note on this voice. Do not be constrained by traditional - * definitions of notes or music. - * - * @param frequency in Hz related to the perceived pitch of the note. - * @param amplitude generally between 0.0 and 1.0 - * @param timeStamp when to play the note - */ - void noteOn(double frequency, double amplitude, TimeStamp timeStamp); - - void noteOff(TimeStamp timeStamp); - - /** - * Typically a UnitVoice will be a subclass of UnitGenerator, which just returns "this". - */ - @Override - public UnitGenerator getUnitGenerator(); - - /** - * Looks up a port using its name and sets the value. - * - * @param portName - * @param value - * @param timeStamp - */ - void setPort(String portName, double value, TimeStamp timeStamp); - - void usePreset(int presetIndex); -} diff --git a/src/com/jsyn/unitgen/Unzipper.java b/src/com/jsyn/unitgen/Unzipper.java deleted file mode 100644 index c776ffb..0000000 --- a/src/com/jsyn/unitgen/Unzipper.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright 2014 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.unitgen; - -/** - * Used inside UnitGenerators for fast smoothing of inputs. - * - * @author Phil Burk (C) 2014 Mobileer Inc - */ -public class Unzipper { - private double target; - private double delta; - private double current; - private int counter; - // About 30 msec. Power of 2 so divide should be faster. - private static final int NUM_STEPS = 1024; - - public double smooth(double input) { - if (input != target) { - target = input; - delta = (target - current) / NUM_STEPS; - counter = NUM_STEPS; - } - if (counter > 0) { - if (--counter == 0) { - current = target; - } else { - current += delta; - } - } - return current; - } -} diff --git a/src/com/jsyn/unitgen/VariableRateDataReader.java b/src/com/jsyn/unitgen/VariableRateDataReader.java deleted file mode 100644 index 2ef163c..0000000 --- a/src/com/jsyn/unitgen/VariableRateDataReader.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * 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.unitgen; - -import com.jsyn.ports.UnitInputPort; - -public abstract class VariableRateDataReader extends SequentialDataReader { - /** A scaler for playback rate. Nominally 1.0. */ - public UnitInputPort rate; - - public VariableRateDataReader() { - super(); - addPort(rate = new UnitInputPort("Rate", 1.0)); - } -} diff --git a/src/com/jsyn/unitgen/VariableRateMonoReader.java b/src/com/jsyn/unitgen/VariableRateMonoReader.java deleted file mode 100644 index 52b7f1e..0000000 --- a/src/com/jsyn/unitgen/VariableRateMonoReader.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * 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.unitgen; - -import com.jsyn.data.FloatSample; -import com.jsyn.data.SegmentedEnvelope; -import com.jsyn.data.ShortSample; -import com.jsyn.ports.UnitOutputPort; - -/** - * This reader can play any SequentialData and will interpolate between adjacent values. It can play - * both {@link SegmentedEnvelope envelopes} and {@link FloatSample samples}. - * - *

-	// Queue an envelope to the dataQueue port.
-	ampEnv.dataQueue.queue(ampEnvelope);
-
- * - * @author Phil Burk (C) 2009 Mobileer Inc - * @see FloatSample - * @see ShortSample - * @see SegmentedEnvelope - */ -public class VariableRateMonoReader extends VariableRateDataReader { - private double phase; // ranges from 0.0 to 1.0 - private double baseIncrement; - private double source; - private double current; - private double target; - private boolean starved; - private boolean ranout; - - public VariableRateMonoReader() { - super(); - addPort(output = new UnitOutputPort("Output")); - starved = true; - baseIncrement = 1.0; - } - - @Override - public void generate(int start, int limit) { - double[] amplitudes = amplitude.getValues(); - double[] rates = rate.getValues(); - double[] outputs = output.getValues(); - - for (int i = start; i < limit; i++) { - // Decrement phase and advance through queued data until phase back - // in range. - if (phase >= 1.0) { - while (phase >= 1.0) { - source = target; - phase -= 1.0; - baseIncrement = advanceToNextFrame(); - } - } else if ((i == 0) && (starved || !dataQueue.isTargetValid())) { - // A starved condition can only be cured at the beginning of a - // block. - source = target = current; - phase = 0.0; - baseIncrement = advanceToNextFrame(); - } - - // Interpolate along line segment. - current = ((target - source) * phase) + source; - outputs[i] = current * amplitudes[i]; - - double phaseIncrement = baseIncrement * rates[i]; - phase += limitPhaseIncrement(phaseIncrement); - } - - if (ranout) { - ranout = false; - if (dataQueue.testAndClearAutoStop()) { - autoStop(); - } - } - } - - public double limitPhaseIncrement(double phaseIncrement) { - return phaseIncrement; - } - - private double advanceToNextFrame() { - // Fire callbacks before getting next value because we already got the - // target value previously. - dataQueue.firePendingCallbacks(); - if (dataQueue.hasMore()) { - starved = false; - target = dataQueue.readNextMonoDouble(getFramePeriod()); - - // calculate phase increment; - return getFramePeriod() * dataQueue.getNormalizedRate(); - } else { - starved = true; - ranout = true; - phase = 0.0; - return 0.0; - } - } - -} diff --git a/src/com/jsyn/unitgen/VariableRateStereoReader.java b/src/com/jsyn/unitgen/VariableRateStereoReader.java deleted file mode 100644 index 0f9fce8..0000000 --- a/src/com/jsyn/unitgen/VariableRateStereoReader.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * 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.unitgen; - -import com.jsyn.ports.UnitOutputPort; - -/** - * This reader can play any SequentialData and will interpolate between adjacent values. It can play - * both envelopes and samples. - * - * @author Phil Burk (C) 2009 Mobileer Inc - */ -public class VariableRateStereoReader extends VariableRateDataReader { - private double phase; - private double baseIncrement; - private double source0; - private double current0; - private double target0; - private double source1; - private double current1; - private double target1; - private boolean starved; - private boolean ranout; - - public VariableRateStereoReader() { - dataQueue.setNumChannels(2); - addPort(output = new UnitOutputPort(2, "Output")); - starved = true; - baseIncrement = 1.0; - } - - @Override - public void generate(int start, int limit) { - double[] amplitudes = amplitude.getValues(); - double[] rates = rate.getValues(); - double[] output0s = output.getValues(0); - double[] output1s = output.getValues(1); - - for (int i = start; i < limit; i++) { - // Decrement phase and advance through queued data until phase back - // in range. - if (phase >= 1.0) { - while (phase >= 1.0) { - source0 = target0; - source1 = target1; - phase -= 1.0; - baseIncrement = advanceToNextFrame(); - } - } else if ((i == 0) && (starved || !dataQueue.isTargetValid())) { - // A starved condition can only be cured at the beginning of a block. - source0 = target0 = current0; - source1 = target1 = current1; - phase = 0.0; - baseIncrement = advanceToNextFrame(); - } - - // Interpolate along line segment. - current0 = ((target0 - source0) * phase) + source0; - output0s[i] = current0 * amplitudes[i]; - current1 = ((target1 - source1) * phase) + source1; - output1s[i] = current1 * amplitudes[i]; - - double phaseIncrement = baseIncrement * rates[i]; - phase += limitPhaseIncrement(phaseIncrement); - } - - if (ranout) { - ranout = false; - if (dataQueue.testAndClearAutoStop()) { - autoStop(); - } - } - } - - public double limitPhaseIncrement(double phaseIncrement) { - return phaseIncrement; - } - - private double advanceToNextFrame() { - dataQueue.firePendingCallbacks(); - if (dataQueue.hasMore()) { - starved = false; - - dataQueue.beginFrame(getFramePeriod()); - target0 = dataQueue.readCurrentChannelDouble(0); - target1 = dataQueue.readCurrentChannelDouble(1); - dataQueue.endFrame(); - - // calculate phase increment; - return synthesisEngine.getFramePeriod() * dataQueue.getNormalizedRate(); - } else { - starved = true; - ranout = true; - phase = 0.0; - return 0.0; - } - } - -} diff --git a/src/com/jsyn/unitgen/WhiteNoise.java b/src/com/jsyn/unitgen/WhiteNoise.java deleted file mode 100644 index b708e92..0000000 --- a/src/com/jsyn/unitgen/WhiteNoise.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 1997 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.unitgen; - -import com.jsyn.ports.UnitInputPort; -import com.jsyn.ports.UnitOutputPort; -import com.jsyn.util.PseudoRandom; - -/** - * WhiteNoise unit. This unit uses a pseudo-random number generator to produce white noise. The - * energy in a white noise signal is distributed evenly across the spectrum. A new random number is - * generated every frame. - * - * @author (C) 1997-2011 Phil Burk, Mobileer Inc - * @see RedNoise - */ -public class WhiteNoise extends UnitGenerator implements UnitSource { - private PseudoRandom randomNum; - public UnitInputPort amplitude; - public UnitOutputPort output; - - public WhiteNoise() { - randomNum = new PseudoRandom(); - addPort(amplitude = new UnitInputPort("Amplitude", UnitOscillator.DEFAULT_AMPLITUDE)); - addPort(output = new UnitOutputPort("Output")); - } - - @Override - public void generate(int start, int limit) { - double[] amplitudes = amplitude.getValues(); - double[] outputs = output.getValues(); - - for (int i = start; i < limit; i++) { - outputs[i] = randomNum.nextRandomDouble() * amplitudes[i]; - } - } - - @Override - public UnitOutputPort getOutput() { - return output; - } -} diff --git a/src/com/jsyn/unitgen/ZeroCrossingCounter.java b/src/com/jsyn/unitgen/ZeroCrossingCounter.java deleted file mode 100644 index 6cd36ea..0000000 --- a/src/com/jsyn/unitgen/ZeroCrossingCounter.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 1997 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.unitgen; - -import com.jsyn.ports.UnitInputPort; -import com.jsyn.ports.UnitOutputPort; - -/** - * Count zero crossings. Handy for unit tests. - * - * @author (C) 1997-2011 Phil Burk, Mobileer Inc - */ -public class ZeroCrossingCounter extends UnitGenerator { - private static final double THRESHOLD = 0.0001; - public UnitInputPort input; - public UnitOutputPort output; - - private long count; - private boolean armed; - - /* Define Unit Ports used by connect() and set(). */ - public ZeroCrossingCounter() { - addPort(input = new UnitInputPort("Input")); - addPort(output = new UnitOutputPort("Output")); - } - - @Override - public void generate(int start, int limit) { - double[] inputs = input.getValues(); - double[] outputs = output.getValues(); - - for (int i = start; i < limit; i++) { - double value = inputs[i]; - if (value < -THRESHOLD) { - armed = true; - } else if (armed & (value > THRESHOLD)) { - ++count; - armed = false; - } - outputs[i] = value; - } - } - - public long getCount() { - return count; - } -} diff --git a/src/com/jsyn/util/AudioSampleLoader.java b/src/com/jsyn/util/AudioSampleLoader.java deleted file mode 100644 index b665933..0000000 --- a/src/com/jsyn/util/AudioSampleLoader.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * 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.util; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.net.URL; - -import com.jsyn.data.FloatSample; - -public interface AudioSampleLoader { - /** - * Load a FloatSample from a File object. - */ - public FloatSample loadFloatSample(File fileIn) throws IOException; - - /** - * Load a FloatSample from an InputStream. This is handy when loading Resources from a JAR file. - */ - public FloatSample loadFloatSample(InputStream inputStream) throws IOException; - - /** - * Load a FloatSample from a URL.. This is handy when loading Resources from a website. - */ - public FloatSample loadFloatSample(URL url) throws IOException; - -} diff --git a/src/com/jsyn/util/AudioStreamReader.java b/src/com/jsyn/util/AudioStreamReader.java deleted file mode 100644 index 5a725c3..0000000 --- a/src/com/jsyn/util/AudioStreamReader.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright 2010 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.util; - -import com.jsyn.Synthesizer; -import com.jsyn.io.AudioFifo; -import com.jsyn.io.AudioInputStream; -import com.jsyn.ports.UnitInputPort; -import com.jsyn.unitgen.MonoStreamWriter; -import com.jsyn.unitgen.StereoStreamWriter; -import com.jsyn.unitgen.UnitStreamWriter; - -/** - * Reads audio signals from the background engine to a foreground application through an AudioFifo. - * Connect to the input port returned by getInput(). - * - * @author Phil Burk (C) 2010 Mobileer Inc - */ -public class AudioStreamReader implements AudioInputStream { - private UnitStreamWriter streamWriter; - private AudioFifo fifo; - - public AudioStreamReader(Synthesizer synth, int samplesPerFrame) { - if (samplesPerFrame == 1) { - streamWriter = new MonoStreamWriter(); - } else if (samplesPerFrame == 2) { - streamWriter = new StereoStreamWriter(); - } else { - throw new IllegalArgumentException("Only 1 or 2 samplesPerFrame supported."); - } - synth.add(streamWriter); - - fifo = new AudioFifo(); - fifo.setWriteWaitEnabled(!synth.isRealTime()); - fifo.setReadWaitEnabled(true); - fifo.allocate(32 * 1024); - streamWriter.setOutputStream(fifo); - streamWriter.start(); - } - - public UnitInputPort getInput() { - return streamWriter.input; - } - - /** How many values are available to read without blocking? */ - @Override - public int available() { - return fifo.available(); - } - - @Override - public void close() { - fifo.close(); - } - - @Override - public double read() { - return fifo.read(); - } - - @Override - public int read(double[] buffer) { - return fifo.read(buffer); - } - - @Override - public int read(double[] buffer, int start, int count) { - return fifo.read(buffer, start, count); - } - -} diff --git a/src/com/jsyn/util/AutoCorrelator.java b/src/com/jsyn/util/AutoCorrelator.java deleted file mode 100644 index 2996036..0000000 --- a/src/com/jsyn/util/AutoCorrelator.java +++ /dev/null @@ -1,293 +0,0 @@ -/* - * Copyright 2004 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.util; - -/** - * Calculate period of a repeated waveform in an array. This algorithm is based on a normalized - * auto-correlation function as dewscribed in: "A Smarter Way to Find Pitch" by Philip McLeod and - * Geoff Wyvill - * - * @author (C) 2004 Mobileer, PROPRIETARY and CONFIDENTIAL - */ -public class AutoCorrelator implements SignalCorrelator { - // A higher number will reject suboctaves more. - private static final float SUB_OCTAVE_REJECTION_FACTOR = 0.0005f; - // We can focus our analysis on the maxima - private static final int STATE_SEEKING_NEGATIVE = 0; - private static final int STATE_SEEKING_POSITIVE = 1; - private static final int STATE_SEEKING_MAXIMUM = 2; - private static final int[] tauAdvanceByState = { - 4, 2, 1 - }; - private int state; - - private float[] buffer; - // double buffer the diffs so we can view them - private float[] diffs; - private float[] diffs1; - private float[] diffs2; - private int cursor = -1; - private int tau; - - private float sumProducts; - private float sumSquares; - private float localMaximum; - private int localPosition; - private float bestMaximum; - private int bestPosition; - private int peakCounter; - // This factor was found empirically to reduce a systematic offset in the pitch. - private float pitchCorrectionFactor = 0.99988f; - - // Results of analysis. - private double period; - private double confidence; - private int minPeriod = 2; - private boolean bufferValid; - private double previousSample = 0.0; - private int maxWindowSize; - private float noiseThreshold = 0.001f; - - public AutoCorrelator(int numFrames) { - buffer = new float[numFrames]; - maxWindowSize = buffer.length / 2; - diffs1 = new float[2 + numFrames / 2]; - diffs2 = new float[diffs1.length]; - diffs = diffs1; - period = minPeriod; - reset(); - } - - // Scan assuming we will not wrap around the buffer. - private void rawDeltaScan(int last1, int last2, int count, int stride) { - for (int k = 0; k < count; k += stride) { - float d1 = buffer[last1 - k]; - float d2 = buffer[last2 - k]; - sumProducts += d1 * d2; - sumSquares += ((d1 * d1) + (d2 * d2)); - } - } - - // Do correlation when we know the splitLast will wrap around. - private void splitDeltaScan(int last1, int splitLast, int count, int stride) { - int c1 = splitLast; - rawDeltaScan(last1, splitLast, c1, stride); - rawDeltaScan(last1 - c1, buffer.length - 1, count - c1, stride); - } - - private void checkDeltaScan(int last1, int last2, int count, int stride) { - if (count > last2) { - int c1 = last2; - // Use recursion with reverse indexes to handle a double split. - checkDeltaScan(last2, last1, c1, stride); - checkDeltaScan(buffer.length - 1, last1 - c1, count - c1, stride); - } else if (count > last1) { - splitDeltaScan(last2, last1, count, stride); - } else { - rawDeltaScan(last1, last2, count, stride); - } - } - - // Perform correlation. Handle circular buffer wrap around. - // Normalized square difference function between -1.0 and +1.0. - private float topScan(int last1, int tau, int count, int stride) { - final float minimumResult = 0.00000001f; - - int last2 = last1 - tau; - if (last2 < 0) { - last2 += buffer.length; - } - sumProducts = 0.0f; - sumSquares = 0.0f; - checkDeltaScan(last1, last2, count, stride); - // Prevent divide by zero. - if (sumSquares < minimumResult) { - return minimumResult; - } - float correction = (float) Math.pow(pitchCorrectionFactor, tau); - float result = (float) (2.0 * sumProducts / sumSquares) * correction; - - return result; - } - - // Prepare for a new calculation. - private void reset() { - switchDiffs(); - int i = 0; - for (; i < minPeriod; i++) { - diffs[i] = 1.0f; - } - for (; i < diffs.length; i++) { - diffs[i] = 0.0f; - } - tau = minPeriod; - state = STATE_SEEKING_NEGATIVE; - peakCounter = 0; - bestMaximum = -1.0f; - bestPosition = -1; - } - - // Analyze new diff result. Incremental peak detection. - private void nextPeakAnalysis(int index) { - // Scale low frequency correlation down to reduce suboctave matching. - // Note that this has a side effect of reducing confidence value for low frequency sounds. - float value = diffs[index] * (1.0f - (index * SUB_OCTAVE_REJECTION_FACTOR)); - switch (state) { - case STATE_SEEKING_NEGATIVE: - if (value < -0.01f) { - state = STATE_SEEKING_POSITIVE; - } - break; - case STATE_SEEKING_POSITIVE: - if (value > 0.2f) { - state = STATE_SEEKING_MAXIMUM; - localMaximum = value; - localPosition = index; - } - break; - case STATE_SEEKING_MAXIMUM: - if (value > localMaximum) { - localMaximum = value; - localPosition = index; - } else if (value < -0.1f) { - peakCounter += 1; - if (localMaximum > bestMaximum) { - bestMaximum = localMaximum; - bestPosition = localPosition; - } - state = STATE_SEEKING_POSITIVE; - } - break; - } - } - - /** - * Generate interpolated maximum from index of absolute maximum using three point analysis. - */ - private double findPreciseMaximum(int indexMax) { - if (indexMax < 3) { - return 3.0; - } - if (indexMax == (diffs.length - 1)) { - return indexMax; - } - // Get 3 adjacent values. - double d1 = diffs[indexMax - 1]; - double d2 = diffs[indexMax]; - double d3 = diffs[indexMax + 1]; - - return interpolatePeak(d1, d2, d3) + indexMax; - } - - // Use quadratic fit to return offset between -0.5 and +0.5 from center. - protected static double interpolatePeak(double d1, double d2, double d3) { - return 0.5 * (d1 - d3) / (d1 - (2.0 * d2) + d3); - } - - // Calculate a little more for each sample. - // This spreads the CPU load out more evenly. - private boolean incrementalAnalysis() { - boolean updated = false; - if (bufferValid) { - // int windowSize = maxWindowSize; - // Interpolate between tau and maxWindowsSize based on confidence. - // If confidence is low then use bigger window. - int windowSize = (int) ((tau * confidence) + (maxWindowSize * (1.0 - confidence))); - - int stride = 1; - // int stride = (windowSize / 32) + 1; - - diffs[tau] = topScan(cursor, tau, windowSize, stride); - - // Check to see if the signal is strong enough to analyze. - // Look at sumPeriods on first correlation. - if ((tau == minPeriod) && (sumProducts < noiseThreshold)) { - // Update if we are dropping to zero confidence. - boolean result = (confidence > 0.0); - confidence = 0.0; - return result; - } - - nextPeakAnalysis(tau); - - // Reuse calculated values if we are not near a peak. - tau += 1; - int advance = tauAdvanceByState[state] - 1; - while ((advance > 0) && (tau < diffs.length)) { - diffs[tau] = diffs[tau - 1]; - tau++; - advance--; - } - - if ((peakCounter >= 4) || (tau >= maxWindowSize)) { - if (bestMaximum > 0.0) { - period = findPreciseMaximum(bestPosition); - // clip into range 0.0 to 1.0, low values are really bogus - confidence = (bestMaximum < 0.0) ? 0.0 : bestMaximum; - } else { - confidence = 0.0; - } - updated = true; - reset(); - } - } - return updated; - } - - @Override - public float[] getDiffs() { - // Return diffs that are not currently being used - return (diffs == diffs1) ? diffs2 : diffs1; - } - - private void switchDiffs() { - diffs = (diffs == diffs1) ? diffs2 : diffs1; - } - - @Override - public boolean addSample(double value) { - double average = (value + previousSample) * 0.5; - previousSample = value; - - cursor += 1; - if (cursor == buffer.length) { - cursor = 0; - bufferValid = true; - } - buffer[cursor] = (float) average; - - return incrementalAnalysis(); - } - - @Override - public double getPeriod() { - return period; - } - - @Override - public double getConfidence() { - return confidence; - } - - public float getPitchCorrectionFactor() { - return pitchCorrectionFactor; - } - - public void setPitchCorrectionFactor(float pitchCorrectionFactor) { - this.pitchCorrectionFactor = pitchCorrectionFactor; - } -} diff --git a/src/com/jsyn/util/Instrument.java b/src/com/jsyn/util/Instrument.java deleted file mode 100644 index 8a53304..0000000 --- a/src/com/jsyn/util/Instrument.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright 2011 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.util; - -import com.softsynth.shared.time.TimeStamp; - -/** - * A note player that references one or more voices by a noteNumber. This is similar to the MIDI - * protocol that references voices by an integer pitch or keyIndex. - * - * @author Phil Burk (C) 2011 Mobileer Inc - */ -public interface Instrument { - // This will be applied to the voice when noteOn is called. - void usePreset(int presetIndex, TimeStamp timeStamp); - - public void noteOn(int tag, double frequency, double amplitude, TimeStamp timeStamp); - - public void noteOff(int tag, TimeStamp timeStamp); - - public void setPort(int tag, String portName, double value, TimeStamp timeStamp); - - public void allNotesOff(TimeStamp timeStamp); -} diff --git a/src/com/jsyn/util/InstrumentLibrary.java b/src/com/jsyn/util/InstrumentLibrary.java deleted file mode 100644 index 65113dc..0000000 --- a/src/com/jsyn/util/InstrumentLibrary.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2011 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.util; - -import com.jsyn.swing.InstrumentBrowser; - -/** - * A library of instruments that can be used to play notes. - * - * @author Phil Burk (C) 2011 Mobileer Inc - * @see InstrumentBrowser - */ - -public interface InstrumentLibrary { - public String getName(); - - public VoiceDescription[] getVoiceDescriptions(); -} diff --git a/src/com/jsyn/util/JavaSoundSampleLoader.java b/src/com/jsyn/util/JavaSoundSampleLoader.java deleted file mode 100644 index 56a654e..0000000 --- a/src/com/jsyn/util/JavaSoundSampleLoader.java +++ /dev/null @@ -1,149 +0,0 @@ -/* - * Copyright 2011 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.util; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.net.URL; - -import javax.sound.sampled.AudioFormat; -import javax.sound.sampled.AudioInputStream; -import javax.sound.sampled.AudioSystem; -import javax.sound.sampled.UnsupportedAudioFileException; - -import com.jsyn.data.FloatSample; - -/** - * Internal class for loading audio samples. Use SampleLoader instead. - * - * @author Phil Burk (C) 2011 Mobileer Inc - */ -class JavaSoundSampleLoader implements AudioSampleLoader { - /** - * Load a FloatSample from a File object. - */ - @Override - public FloatSample loadFloatSample(File fileIn) throws IOException { - try { - return loadFloatSample(AudioSystem.getAudioInputStream(fileIn)); - } catch (UnsupportedAudioFileException e) { - throw new IOException(e); - } - } - - /** - * Load a FloatSample from an InputStream. This is handy when loading Resources from a JAR file. - */ - @Override - public FloatSample loadFloatSample(InputStream inputStream) throws IOException { - try { - return loadFloatSample(AudioSystem.getAudioInputStream(inputStream)); - } catch (UnsupportedAudioFileException e) { - throw new IOException(e); - } - } - - /** - * Load a FloatSample from a URL.. This is handy when loading Resources from a website. - */ - @Override - public FloatSample loadFloatSample(URL url) throws IOException { - try { - return loadFloatSample(AudioSystem.getAudioInputStream(url)); - } catch (UnsupportedAudioFileException e) { - throw new IOException(e); - } - } - - private FloatSample loadFloatSample(javax.sound.sampled.AudioInputStream audioInputStream) - throws IOException, UnsupportedAudioFileException { - float[] floatData = null; - FloatSample sample = null; - int bytesPerFrame = audioInputStream.getFormat().getFrameSize(); - if (bytesPerFrame == AudioSystem.NOT_SPECIFIED) { - // some audio formats may have unspecified frame size - // in that case we may read any amount of bytes - bytesPerFrame = 1; - } - AudioFormat format = audioInputStream.getFormat(); - if (format.getEncoding() == AudioFormat.Encoding.PCM_SIGNED) { - floatData = loadSignedPCM(audioInputStream); - } - sample = new FloatSample(floatData, format.getChannels()); - sample.setFrameRate(format.getFrameRate()); - return sample; - } - - private float[] loadSignedPCM(AudioInputStream audioInputStream) throws IOException, - UnsupportedAudioFileException { - int totalSamplesRead = 0; - AudioFormat format = audioInputStream.getFormat(); - int numFrames = (int) audioInputStream.getFrameLength(); - int numSamples = format.getChannels() * numFrames; - float[] data = new float[numSamples]; - final int bytesPerFrame = format.getFrameSize(); - // Set an arbitrary buffer size of 1024 frames. - int numBytes = 1024 * bytesPerFrame; - byte[] audioBytes = new byte[numBytes]; - int numBytesRead = 0; - int numFramesRead = 0; - // Try to read numBytes bytes from the file. - while ((numBytesRead = audioInputStream.read(audioBytes)) != -1) { - int bytesRemainder = numBytesRead % bytesPerFrame; - if (bytesRemainder != 0) { - // TODO Read until you get enough data. - throw new IOException("Read partial block of sample data!"); - } - - if (audioInputStream.getFormat().getSampleSizeInBits() == 16) { - if (format.isBigEndian()) { - SampleLoader.decodeBigI16ToF32(audioBytes, 0, numBytesRead, data, - totalSamplesRead); - } else { - SampleLoader.decodeLittleI16ToF32(audioBytes, 0, numBytesRead, data, - totalSamplesRead); - } - } else if (audioInputStream.getFormat().getSampleSizeInBits() == 24) { - if (format.isBigEndian()) { - SampleLoader.decodeBigI24ToF32(audioBytes, 0, numBytesRead, data, - totalSamplesRead); - } else { - SampleLoader.decodeLittleI24ToF32(audioBytes, 0, numBytesRead, data, - totalSamplesRead); - } - } else if (audioInputStream.getFormat().getSampleSizeInBits() == 32) { - if (format.isBigEndian()) { - SampleLoader.decodeBigI32ToF32(audioBytes, 0, numBytesRead, data, - totalSamplesRead); - } else { - SampleLoader.decodeLittleI32ToF32(audioBytes, 0, numBytesRead, data, - totalSamplesRead); - } - } else { - throw new UnsupportedAudioFileException( - "Only 16, 24 or 32 bit PCM samples supported."); - } - - // Calculate the number of frames actually read. - numFramesRead = numBytesRead / bytesPerFrame; - totalSamplesRead += numFramesRead * format.getChannels(); - } - return data; - } - -} diff --git a/src/com/jsyn/util/JavaTools.java b/src/com/jsyn/util/JavaTools.java deleted file mode 100644 index 65e3dd1..0000000 --- a/src/com/jsyn/util/JavaTools.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * 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.util; - -public class JavaTools { - - @SuppressWarnings("rawtypes") - public static Class loadClass(String className, boolean verbose) { - Class newClass = null; - try { - newClass = Class.forName(className); - } catch (Throwable e) { - if (verbose) - System.out.println("Caught " + e); - } - if (newClass == null) { - try { - ClassLoader systemLoader = ClassLoader.getSystemClassLoader(); - newClass = Class.forName(className, true, systemLoader); - } catch (Throwable e) { - if (verbose) - System.out.println("Caught " + e); - } - } - return newClass; - } - - /** - * First try Class.forName(). If this fails, try Class.forName() using - * ClassLoader.getSystemClassLoader(). - * - * @return Class or null - */ - @SuppressWarnings("rawtypes") - public static Class loadClass(String className) { - /** - * First try Class.forName(). If this fails, try Class.forName() using - * ClassLoader.getSystemClassLoader(). - * - * @return Class or null - */ - return loadClass(className, true); - } - -} diff --git a/src/com/jsyn/util/MultiChannelSynthesizer.java b/src/com/jsyn/util/MultiChannelSynthesizer.java deleted file mode 100644 index b84d753..0000000 --- a/src/com/jsyn/util/MultiChannelSynthesizer.java +++ /dev/null @@ -1,404 +0,0 @@ -/* - * Copyright 2016 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.util; - -import com.jsyn.Synthesizer; -import com.jsyn.engine.SynthesisEngine; -import com.jsyn.midi.MidiConstants; -import com.jsyn.ports.UnitInputPort; -import com.jsyn.ports.UnitOutputPort; -import com.jsyn.unitgen.ExponentialRamp; -import com.jsyn.unitgen.LinearRamp; -import com.jsyn.unitgen.Multiply; -import com.jsyn.unitgen.Pan; -import com.jsyn.unitgen.PowerOfTwo; -import com.jsyn.unitgen.SineOscillator; -import com.jsyn.unitgen.TwoInDualOut; -import com.jsyn.unitgen.UnitGenerator; -import com.jsyn.unitgen.UnitOscillator; -import com.jsyn.unitgen.UnitVoice; -import com.softsynth.math.AudioMath; -import com.softsynth.shared.time.TimeStamp; - -/** - * General purpose synthesizer with "channels" - * that could be used to implement a MIDI synthesizer. - * - * Each channel has: - *

- * lfo -> pitchToLinear -> [VOICES] -> volume* -> panner
- * bend --/
- * 
- * - * Note: this class is experimental and subject to change. - * - * @author Phil Burk (C) 2016 Mobileer Inc - */ -public class MultiChannelSynthesizer { - private Synthesizer synth; - private TwoInDualOut outputUnit; - private ChannelContext[] channels; - private final static int MAX_VELOCITY = 127; - private double mMasterAmplitude = 0.25; - - private class ChannelGroupContext { - private VoiceDescription voiceDescription; - private UnitVoice[] voices; - private VoiceAllocator allocator; - - ChannelGroupContext(int numVoices, VoiceDescription voiceDescription) { - this.voiceDescription = voiceDescription; - - voices = new UnitVoice[numVoices]; - for (int i = 0; i < numVoices; i++) { - UnitVoice voice = voiceDescription.createUnitVoice(); - UnitGenerator ugen = voice.getUnitGenerator(); - synth.add(ugen); - voices[i] = voice; - - } - allocator = new VoiceAllocator(voices); - } - } - - private class ChannelContext { - private UnitOscillator lfo; - private PowerOfTwo pitchToLinear; - private LinearRamp timbreRamp; - private LinearRamp pressureRamp; - private ExponentialRamp volumeRamp; - private Multiply volumeMultiplier; - private Pan panner; - private double vibratoRate = 5.0; - private double bendRangeOctaves = 2.0 / 12.0; - private int presetIndex; - private ChannelGroupContext groupContext; - VoiceOperation voiceOperation = new VoiceOperation() { - @Override - public void operate (UnitVoice voice) { - voice.usePreset(presetIndex); - connectVoice(voice); - } - }; - - void setup(ChannelGroupContext groupContext) { - this.groupContext = groupContext; - synth.add(pitchToLinear = new PowerOfTwo()); - synth.add(lfo = new SineOscillator()); // TODO use a MorphingOscillator or switch - // between S&H etc. - // Use a ramp to smooth out the timbre changes. - // This helps reduce pops from changing filter cutoff too abruptly. - synth.add(timbreRamp = new LinearRamp()); - timbreRamp.time.set(0.02); - synth.add(pressureRamp = new LinearRamp()); - pressureRamp.time.set(0.02); - synth.add(volumeRamp = new ExponentialRamp()); - volumeRamp.input.set(1.0); - volumeRamp.time.set(0.02); - synth.add(volumeMultiplier = new Multiply()); - synth.add(panner = new Pan()); - - pitchToLinear.input.setValueAdded(true); // so we can sum pitch bend - lfo.output.connect(pitchToLinear.input); - lfo.amplitude.set(0.0); - lfo.frequency.set(vibratoRate); - - volumeRamp.output.connect(volumeMultiplier.inputB); - volumeMultiplier.output.connect(panner.input); - panner.output.connect(0, outputUnit.inputA, 0); // Use MultiPassthrough - panner.output.connect(1, outputUnit.inputB, 0); - } - - private void connectVoice(UnitVoice voice) { - UnitGenerator ugen = voice.getUnitGenerator(); - // Hook up some channel controllers to standard ports on the voice. - UnitInputPort freqMod = (UnitInputPort) ugen - .getPortByName(UnitGenerator.PORT_NAME_FREQUENCY_SCALER); - if (freqMod != null) { - freqMod.disconnectAll(); - pitchToLinear.output.connect(freqMod); - } - UnitInputPort timbrePort = (UnitInputPort) ugen - .getPortByName(UnitGenerator.PORT_NAME_TIMBRE); - if (timbrePort != null) { - timbrePort.disconnectAll(); - timbreRamp.output.connect(timbrePort); - timbreRamp.input.setup(timbrePort); - } - UnitInputPort pressurePort = (UnitInputPort) ugen - .getPortByName(UnitGenerator.PORT_NAME_PRESSURE); - if (pressurePort != null) { - pressurePort.disconnectAll(); - pressureRamp.output.connect(pressurePort); - pressureRamp.input.setup(pressurePort); - } - voice.getOutput().disconnectAll(); - voice.getOutput().connect(volumeMultiplier.inputA); // mono mix all the voices - } - - void programChange(int program) { - int programWrapped = program % groupContext.voiceDescription.getPresetCount(); - String name = groupContext.voiceDescription.getPresetNames()[programWrapped]; - //System.out.println("Preset[" + program + "] = " + name); - presetIndex = programWrapped; - } - - void noteOff(int noteNumber, double amplitude) { - groupContext.allocator.noteOff(noteNumber, synth.createTimeStamp()); - } - - void noteOff(int noteNumber, double amplitude, TimeStamp timeStamp) { - groupContext.allocator.noteOff(noteNumber, timeStamp); - } - - void noteOn(int noteNumber, double amplitude) { - noteOn(noteNumber, amplitude, synth.createTimeStamp()); - } - - void noteOn(int noteNumber, double amplitude, TimeStamp timeStamp) { - double frequency = AudioMath.pitchToFrequency(noteNumber); - //System.out.println("noteOn(noteNumber) -> " + frequency + " Hz"); - groupContext.allocator.noteOn(noteNumber, frequency, amplitude, voiceOperation, timeStamp); - } - - public void setPitchBend(double offset) { - pitchToLinear.input.set(bendRangeOctaves * offset); - } - - public void setBendRange(double semitones) { - bendRangeOctaves = semitones / 12.0; - } - - public void setVibratoDepth(double semitones) { - lfo.amplitude.set(semitones); - } - - public void setVolume(double volume) { - double min = SynthesisEngine.DB96; - double max = 1.0; - double ratio = max / min; - double value = min * Math.pow(ratio, volume); - volumeRamp.input.set(value); - } - - public void setPan(double pan) { - panner.pan.set(pan); - } - - /* - * @param timbre normalized 0 to 1 - */ - public void setTimbre(double timbre) { - double min = timbreRamp.input.getMinimum(); - double max = timbreRamp.input.getMaximum(); - double value = min + (timbre * (max - min)); - timbreRamp.input.set(value); - } - - /* - * @param pressure normalized 0 to 1 - */ - public void setPressure(double pressure) { - double min = pressureRamp.input.getMinimum(); - double max = pressureRamp.input.getMaximum(); - double ratio = max / min; - double value = min * Math.pow(ratio, pressure); - pressureRamp.input.set(value); - } - } - - /** - * Construct a synthesizer with a maximum of 16 channels like MIDI. - */ - public MultiChannelSynthesizer() { - this(MidiConstants.MAX_CHANNELS); - } - - - public MultiChannelSynthesizer(int maxChannels) { - channels = new ChannelContext[maxChannels]; - for (int i = 0; i < channels.length; i++) { - channels[i] = new ChannelContext(); - } - } - - /** - * Specify a VoiceDescription to use with multiple channels. - * - * @param synth - * @param startChannel channel index is zero based - * @param numChannels - * @param voicesPerChannel - * @param voiceDescription - */ - public void setup(Synthesizer synth, int startChannel, int numChannels, int voicesPerChannel, - VoiceDescription voiceDescription) { - this.synth = synth; - if (outputUnit == null) { - synth.add(outputUnit = new TwoInDualOut()); - } - ChannelGroupContext groupContext = new ChannelGroupContext(voicesPerChannel, - voiceDescription); - for (int i = 0; i < numChannels; i++) { - channels[startChannel + i].setup(groupContext); - } - } - - public void programChange(int channel, int program) { - ChannelContext channelContext = channels[channel]; - channelContext.programChange(program); - } - - - /** - * Turn off a note. - * @param channel - * @param noteNumber - * @param velocity between 0 and 127, will be scaled by masterAmplitude - */ - public void noteOff(int channel, int noteNumber, int velocity) { - double amplitude = velocity * (1.0 / MAX_VELOCITY); - noteOff(channel, noteNumber, amplitude); - } - - /** - * Turn off a note. - * @param channel - * @param noteNumber - * @param amplitude between 0 and 1.0, will be scaled by masterAmplitude - */ - public void noteOff(int channel, int noteNumber, double amplitude) { - ChannelContext channelContext = channels[channel]; - channelContext.noteOff(noteNumber, amplitude * mMasterAmplitude); - } - - /** - * Turn off a note. - * @param channel - * @param noteNumber - * @param amplitude between 0 and 1.0, will be scaled by masterAmplitude - */ - public void noteOff(int channel, int noteNumber, double amplitude, TimeStamp timeStamp) { - ChannelContext channelContext = channels[channel]; - channelContext.noteOff(noteNumber, amplitude * mMasterAmplitude, timeStamp); - } - - /** - * Turn on a note. - * @param channel - * @param noteNumber - * @param velocity between 0 and 127, will be scaled by masterAmplitude - */ - public void noteOn(int channel, int noteNumber, int velocity) { - double amplitude = velocity * (1.0 / MAX_VELOCITY); - noteOn(channel, noteNumber, amplitude); - } - - /** - * Turn on a note. - * @param channel - * @param noteNumber - * @param amplitude between 0 and 1.0, will be scaled by masterAmplitude - */ - public void noteOn(int channel, int noteNumber, double amplitude, TimeStamp timeStamp) { - ChannelContext channelContext = channels[channel]; - channelContext.noteOn(noteNumber, amplitude * mMasterAmplitude, timeStamp); - } - - /** - * Turn on a note. - * @param channel - * @param noteNumber - * @param amplitude between 0 and 1.0, will be scaled by masterAmplitude - */ - public void noteOn(int channel, int noteNumber, double amplitude) { - ChannelContext channelContext = channels[channel]; - channelContext.noteOn(noteNumber, amplitude * mMasterAmplitude); - } - - /** - * Set a pitch offset that will be scaled by the range for the channel. - * - * @param channel - * @param offset ranges from -1.0 to +1.0 - */ - public void setPitchBend(int channel, double offset) { - //System.out.println("setPitchBend[" + channel + "] = " + offset); - ChannelContext channelContext = channels[channel]; - channelContext.setPitchBend(offset); - } - - public void setBendRange(int channel, double semitones) { - ChannelContext channelContext = channels[channel]; - channelContext.setBendRange(semitones); - } - - public void setPressure(int channel, double pressure) { - ChannelContext channelContext = channels[channel]; - channelContext.setPressure(pressure); - } - - public void setVibratoDepth(int channel, double semitones) { - ChannelContext channelContext = channels[channel]; - channelContext.setVibratoDepth(semitones); - } - - public void setTimbre(int channel, double timbre) { - ChannelContext channelContext = channels[channel]; - channelContext.setTimbre(timbre); - } - - /** - * Set volume for entire channel. - * - * @param channel - * @param volume normalized between 0.0 and 1.0 - */ - public void setVolume(int channel, double volume) { - ChannelContext channelContext = channels[channel]; - channelContext.setVolume(volume); - } - - /** - * Pan from left to right. - * - * @param channel - * @param pan ranges from -1.0 to +1.0 - */ - public void setPan(int channel, double pan) { - ChannelContext channelContext = channels[channel]; - channelContext.setPan(pan); - } - - /** - * @return stereo output port - */ - public UnitOutputPort getOutput() { - return outputUnit.output; - } - - /** - * Set amplitude for a single voice when the velocity is 127. - * @param masterAmplitude - */ - public void setMasterAmplitude(double masterAmplitude) { - mMasterAmplitude = masterAmplitude; - } - public double getMasterAmplitude() { - return mMasterAmplitude; - } -} diff --git a/src/com/jsyn/util/NumericOutput.java b/src/com/jsyn/util/NumericOutput.java deleted file mode 100644 index 79afaf1..0000000 --- a/src/com/jsyn/util/NumericOutput.java +++ /dev/null @@ -1,187 +0,0 @@ -/* - * Copyright 1999 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.util; - -/** - * Formatted numeric output. Convert integers and floats to strings based on field widths and - * desired decimal places. - * - * @author Phil Burk (C) 1999 SoftSynth.com - */ - -public class NumericOutput { - static char digitToChar(int digit) { - if (digit > 9) { - return (char) ('A' + digit - 10); - } else { - return (char) ('0' + digit); - } - } - - public static String integerToString(int n, int width, boolean leadingZeros) { - return integerToString(n, width, leadingZeros, 10); - } - - public static String integerToString(int n, int width) { - return integerToString(n, width, false, 10); - } - - public static String integerToString(int n, int width, boolean leadingZeros, int radix) { - if (width > 32) - width = 32; - StringBuffer buf = new StringBuffer(); - long ln = n; - boolean ifNeg = false; - // only do sign if decimal - if (radix != 10) { - // System.out.println("MASK before : ln = " + ln ); - ln = ln & 0x00000000FFFFFFFFL; - // System.out.println("MASK after : ln = " + ln ); - } else if (ln < 0) { - ifNeg = true; - ln = -ln; - } - if (ln == 0) { - buf.append('0'); - } else { - // System.out.println(" ln = " + ln ); - while (ln > 0) { - int rem = (int) (ln % radix); - buf.append(digitToChar(rem)); - ln = ln / radix; - } - } - if (leadingZeros) { - int pl = width; - if (ifNeg) - pl -= 1; - for (int i = buf.length(); i < pl; i++) - buf.append('0'); - } - if (ifNeg) - buf.append('-'); - // leading spaces - for (int i = buf.length(); i < width; i++) - buf.append(' '); - // reverse buffer to put characters in correct order - buf.reverse(); - - return buf.toString(); - } - - /** - * Convert double to string. - * - * @param width = minimum width of formatted string - * @param places = number of digits displayed after decimal point - */ - public static String doubleToString(double value, int width, int places) { - return doubleToString(value, width, places, false); - } - - /** - * Convert double to string. - * - * @param width = minimum width of formatted string - * @param places = number of digits displayed after decimal point - */ - public static String doubleToString(double value, int width, int places, boolean leadingZeros) { - if (width > 32) - width = 32; - if (places > 16) - places = 16; - - boolean ifNeg = false; - if (value < 0.0) { - ifNeg = true; - value = -value; - } - // round at relevant decimal place - value += 0.5 * Math.pow(10.0, 0 - places); - int ival = (int) Math.floor(value); - // get portion after decimal point as an integer - int fval = (int) ((value - Math.floor(value)) * Math.pow(10.0, places)); - String result = ""; - - result += integerToString(ival, 0, false, 10); - result += "."; - result += integerToString(fval, places, true, 10); - - if (leadingZeros) { - // prepend leading zeros and {-} - int zw = width; - if (ifNeg) - zw -= 1; - while (result.length() < zw) - result = "0" + result; - if (ifNeg) - result = "-" + result; - } else { - // prepend {-} and leading spaces - if (ifNeg) - result = "-" + result; - while (result.length() < width) - result = " " + result; - } - return result; - } - - static void testInteger(int n) { - System.out.println("Test " + n + ", 0x" + Integer.toHexString(n) + ", %" - + Integer.toBinaryString(n)); - System.out.println(" +,8,t,10 = " + integerToString(n, 8, true, 10)); - System.out.println(" +,8,f,10 = " + integerToString(n, 8, false, 10)); - System.out.println(" -,8,t,10 = " + integerToString(-n, 8, true, 10)); - System.out.println(" -,8,f,10 = " + integerToString(-n, 8, false, 10)); - System.out.println(" +,8,t,16 = " + integerToString(n, 8, true, 16)); - System.out.println(" +,8,f,16 = " + integerToString(n, 8, false, 16)); - System.out.println(" -,8,t,16 = " + integerToString(-n, 8, true, 16)); - System.out.println(" -,8,f,16 = " + integerToString(-n, 8, false, 16)); - System.out.println(" +,8,t, 2 = " + integerToString(n, 8, true, 2)); - System.out.println(" +,8,f, 2 = " + integerToString(n, 8, false, 2)); - } - - static void testDouble(double value) { - System.out.println("Test " + value); - System.out.println(" +,5,1 = " + doubleToString(value, 5, 1)); - System.out.println(" -,5,1 = " + doubleToString(-value, 5, 1)); - - System.out.println(" +,14,3 = " + doubleToString(value, 14, 3)); - System.out.println(" -,14,3 = " + doubleToString(-value, 14, 3)); - - System.out.println(" +,6,2,true = " + doubleToString(value, 6, 2, true)); - System.out.println(" -,6,2,true = " + doubleToString(-value, 6, 2, true)); - } - - public static void main(String argv[]) { - System.out.println("Test NumericOutput"); - testInteger(0); - testInteger(1); - testInteger(16); - testInteger(23456); - testInteger(0x23456); - testInteger(0x89ABC); - testDouble(0.0); - testDouble(0.0678); - testDouble(0.1234567); - testDouble(1.234567); - testDouble(12.34567); - testDouble(123.4567); - testDouble(1234.5678); - - } -} diff --git a/src/com/jsyn/util/PolyphonicInstrument.java b/src/com/jsyn/util/PolyphonicInstrument.java deleted file mode 100644 index 08460d0..0000000 --- a/src/com/jsyn/util/PolyphonicInstrument.java +++ /dev/null @@ -1,155 +0,0 @@ -/* - * Copyright 2011 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.util; - -import com.jsyn.ports.UnitInputPort; -import com.jsyn.ports.UnitOutputPort; -import com.jsyn.ports.UnitPort; -import com.jsyn.unitgen.Circuit; -import com.jsyn.unitgen.Multiply; -import com.jsyn.unitgen.PassThrough; -import com.jsyn.unitgen.UnitGenerator; -import com.jsyn.unitgen.UnitSource; -import com.jsyn.unitgen.UnitVoice; -import com.softsynth.shared.time.TimeStamp; - -/** - * The API for this class is likely to change. Please comment on its usefulness. - * - * @author Phil Burk (C) 2011 Mobileer Inc - */ - -public class PolyphonicInstrument extends Circuit implements UnitSource, Instrument { - private Multiply mixer; - private UnitVoice[] voices; - private VoiceAllocator voiceAllocator; - public UnitInputPort amplitude; - - public PolyphonicInstrument(UnitVoice[] voices) { - this.voices = voices; - voiceAllocator = new VoiceAllocator(voices); - add(mixer = new Multiply()); - // Mix all the voices to one output. - for (UnitVoice voice : voices) { - UnitGenerator unit = voice.getUnitGenerator(); - boolean wasEnabled = unit.isEnabled(); - // This overrides the enabled property of the voice. - add(unit); - voice.getOutput().connect(mixer.inputA); - // restore - unit.setEnabled(wasEnabled); - } - - addPort(amplitude = mixer.inputB, "Amplitude"); - amplitude.setup(0.0001, 0.4, 2.0); - exportAllInputPorts(); - } - - /** - * Connect a PassThrough unit to the input ports of the voices so that they can be controlled - * together using a single port. Note that this will prevent their individual use. So the - * "Frequency" and "Amplitude" ports are excluded. Note that this method is a bit funky and is - * likely to change. - */ - public void exportAllInputPorts() { - // Iterate through the ports. - for (UnitPort port : voices[0].getUnitGenerator().getPorts()) { - if (port instanceof UnitInputPort) { - UnitInputPort inputPort = (UnitInputPort) port; - String voicePortName = inputPort.getName(); - // FIXME Need better way to identify ports that are per note. - if (!voicePortName.equals("Frequency") && !voicePortName.equals("Amplitude")) { - exportNamedInputPort(voicePortName); - } - } - } - } - - /** - * Create a UnitInputPort for the circuit that is connected to the named port on each voice - * through a PassThrough unit. This allows you to control all of the voices at once. - * - * @param portName - * @see exportAllInputPorts - */ - public void exportNamedInputPort(String portName) { - UnitInputPort voicePort = null; - PassThrough fanout = new PassThrough(); - for (UnitVoice voice : voices) { - voicePort = (UnitInputPort) voice.getUnitGenerator().getPortByName(portName); - fanout.output.connect(voicePort); - } - if (voicePort != null) { - addPort(fanout.input, portName); - fanout.input.setup(voicePort); - } - } - - @Override - public UnitOutputPort getOutput() { - return mixer.output; - } - - @Override - public void usePreset(int presetIndex) { - usePreset(presetIndex, getSynthesisEngine().createTimeStamp()); - } - - // FIXME - no timestamp on UnitVoice - @Override - public void usePreset(int presetIndex, TimeStamp timeStamp) { - // Apply preset to all voices. - for (UnitVoice voice : voices) { - voice.usePreset(presetIndex); - } - // Then copy values from first voice to instrument. - for (UnitPort port : voices[0].getUnitGenerator().getPorts()) { - if (port instanceof UnitInputPort) { - UnitInputPort inputPort = (UnitInputPort) port; - // FIXME Need better way to identify ports that are per note. - UnitInputPort fanPort = (UnitInputPort) getPortByName(inputPort.getName()); - if ((fanPort != null) && (fanPort != amplitude)) { - fanPort.set(inputPort.get()); - } - } - } - } - - @Override - public void noteOn(int tag, double frequency, double amplitude, TimeStamp timeStamp) { - voiceAllocator.noteOn(tag, frequency, amplitude, timeStamp); - } - - @Override - public void noteOff(int tag, TimeStamp timeStamp) { - voiceAllocator.noteOff(tag, timeStamp); - } - - @Override - public void setPort(int tag, String portName, double value, TimeStamp timeStamp) { - voiceAllocator.setPort(tag, portName, value, timeStamp); - } - - @Override - public void allNotesOff(TimeStamp timeStamp) { - voiceAllocator.allNotesOff(timeStamp); - } - - public synchronized boolean isOn(int tag) { - return voiceAllocator.isOn(tag); - } -} diff --git a/src/com/jsyn/util/PseudoRandom.java b/src/com/jsyn/util/PseudoRandom.java deleted file mode 100644 index e92b669..0000000 --- a/src/com/jsyn/util/PseudoRandom.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * 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. - */ -/** - * Sep 9, 2009 - * com.jsyn.engine.units.SynthRandom.java - */ - -package com.jsyn.util; - -import java.util.Random; - -/** - * Pseudo-random numbers using predictable and fast linear-congruential method. - * - * @author Phil Burk (C) 2009 Mobileer Inc Translated from 'C' to Java by Lisa - * Tolentino. - */ -public class PseudoRandom { - // We must shift 1L or else we get a negative number! - private static final double INT_TO_DOUBLE = (1.0 / (1L << 31)); - private long seed = 99887766; - - /** - * Create an instance of SynthRandom. - */ - public PseudoRandom() { - this(new Random().nextInt()); - } - - /** - * Create an instance of PseudoRandom. - */ - public PseudoRandom(int seed) { - setSeed(seed); - } - - public void setSeed(int seed) { - this.seed = (long) seed; - } - - public int getSeed() { - return (int) seed; - } - - /** - * Returns the next random double from 0.0 to 1.0 - * - * @return value from 0.0 to 1.0 - */ - public double random() { - int positiveInt = nextRandomInteger() & 0x7FFFFFFF; - return positiveInt * INT_TO_DOUBLE; - } - - /** - * Returns the next random double from -1.0 to 1.0 - * - * @return value from -1.0 to 1.0 - */ - public double nextRandomDouble() { - return nextRandomInteger() * INT_TO_DOUBLE; - } - - /** Calculate random 32 bit number using linear-congruential method. */ - public int nextRandomInteger() { - // Use values for 64-bit sequence from MMIX by Donald Knuth. - seed = (seed * 6364136223846793005L) + 1442695040888963407L; - return (int) (seed >> 32); // The higher bits have a longer sequence. - } - - public int choose(int range) { - long positiveInt = nextRandomInteger() & 0x7FFFFFFF; - long temp = positiveInt * range; - return (int) (temp >> 31); - } -} diff --git a/src/com/jsyn/util/RecursiveSequenceGenerator.java b/src/com/jsyn/util/RecursiveSequenceGenerator.java deleted file mode 100644 index 53dbdf9..0000000 --- a/src/com/jsyn/util/RecursiveSequenceGenerator.java +++ /dev/null @@ -1,214 +0,0 @@ -/* - * Copyright 1997 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.util; - -import java.util.Random; - -/** - * Generate a sequence of integers based on a recursive mining of previous material. Notes are - * generated by one of the following formula: - * - *
- * 
- * value[n] = value[n-delay] + offset;
- * 
- * 
- * - * The parameters delay and offset are randomly generated. This algorithm was first developed in - * 1977 for a class project in FORTRAN. It was ported to Forth for HMSL in the late 80's. It was - * then ported to Java for JSyn in 1997. - * - * @author Phil Burk (C) 1997,2011 Mobileer Inc - */ -public class RecursiveSequenceGenerator { - private int delay = 1; - private int maxValue; - private int maxInterval; - private double desiredDensity = 0.5; - - private int offset; - private int values[]; - private boolean enables[]; - private int cursor; - private int countdown = -1; - private double actualDensity; - private int beatsPerMeasure = 8; - private Random random; - - public RecursiveSequenceGenerator() { - this(25, 7, 64); - } - - public RecursiveSequenceGenerator(int maxValue, int maxInterval, int arraySize) { - values = new int[arraySize]; - enables = new boolean[arraySize]; - this.maxValue = maxValue; - this.maxInterval = maxInterval; - for (int i = 0; i < values.length; i++) { - values[i] = maxValue / 2; - enables[i] = isNextEnabled(false); - } - } - - /** Set density of notes. 0.0 to 1.0 */ - public void setDensity(double density) { - desiredDensity = density; - } - - public double getDensity() { - return desiredDensity; - } - - /** Set maximum for generated value. */ - public void setMaxValue(int maxValue) { - this.maxValue = maxValue; - } - - public int getMaxValue() { - return maxValue; - } - - /** Set maximum for generated value. */ - public void setMaxInterval(int maxInterval) { - this.maxInterval = maxInterval; - } - - public int getMaxInterval() { - return maxInterval; - } - - /* Determine whether next in sequence should occur. */ - public boolean isNextEnabled(boolean preferance) { - /* Calculate note density using low pass IIR filter. */ - double newDensity = (actualDensity * 0.9) + (preferance ? 0.1 : 0.0); - /* Invert enable to push density towards desired level, with hysteresis. */ - if (preferance && (newDensity > ((desiredDensity * 0.7) + 0.3))) - preferance = false; - else if (!preferance && (newDensity < (desiredDensity * 0.7))) - preferance = true; - actualDensity = (actualDensity * 0.9) + (preferance ? 0.1 : 0.0); - return preferance; - } - - public int randomPowerOf2(int maxExp) { - return (1 << (int) (random.nextDouble() * (maxExp + 1))); - } - - /** Random number evenly distributed from -maxInterval to +maxInterval */ - public int randomEvenInterval() { - return (int) (random.nextDouble() * ((maxInterval * 2) + 1)) - maxInterval; - } - - void calcNewOffset() { - offset = randomEvenInterval(); - } - - public void randomize() { - - delay = randomPowerOf2(4); - calcNewOffset(); - // System.out.println("NewSeq: delay = " + delay + ", offset = " + - // offset ); - } - - /** Change parameters based on random countdown. */ - public int next() { - // If this sequence is finished, start a new one. - if (countdown-- < 0) { - randomize(); - countdown = randomPowerOf2(3); - } - return nextValue(); - } - - /** Change parameters using a probability based on beatIndex. */ - public int next(int beatIndex) { - int beatMod = beatIndex % beatsPerMeasure; - switch (beatMod) { - case 0: - if (Math.random() < 0.90) - randomize(); - break; - case 2: - case 6: - if (Math.random() < 0.15) - randomize(); - break; - case 4: - if (Math.random() < 0.30) - randomize(); - break; - default: - if (Math.random() < 0.07) - randomize(); - break; - } - return nextValue(); - } - - /** Generate nextValue based on current delay and offset */ - public int nextValue() { - // Generate index into circular value buffer. - int idx = (cursor - delay); - if (idx < 0) - idx += values.length; - - // Generate new value. Calculate new offset if too high or low. - int nextVal = 0; - int timeout = 100; - while (timeout > 0) { - nextVal = values[idx] + offset; - if ((nextVal >= 0) && (nextVal < maxValue)) - break; - // Prevent endless loops when maxValue changes. - if (nextVal > (maxValue + maxInterval - 1)) { - nextVal = maxValue; - break; - } - calcNewOffset(); - timeout--; - // System.out.println("NextVal = " + nextVal + ", offset = " + - // offset ); - } - if (timeout <= 0) { - System.err.println("RecursiveSequence: nextValue timed out. offset = " + offset); - nextVal = maxValue / 2; - offset = 0; - } - - // Save new value in circular buffer. - values[cursor] = nextVal; - - boolean playIt = enables[cursor] = isNextEnabled(enables[idx]); - cursor++; - if (cursor >= values.length) - cursor = 0; - - // System.out.println("nextVal = " + nextVal ); - - return playIt ? nextVal : -1; - } - - public Random getRandom() { - return random; - } - - public void setRandom(Random random) { - this.random = random; - } - -} diff --git a/src/com/jsyn/util/SampleLoader.java b/src/com/jsyn/util/SampleLoader.java deleted file mode 100644 index 170b4cb..0000000 --- a/src/com/jsyn/util/SampleLoader.java +++ /dev/null @@ -1,230 +0,0 @@ -/* - * Copyright 2011 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.util; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.net.URL; - -import com.jsyn.data.FloatSample; -import com.jsyn.util.soundfile.CustomSampleLoader; - -/** - * Load a FloatSample from various sources. The default loader uses custom code to load WAV or AIF - * files. Supported data formats are 16, 24 and 32 bit PCM, and 32-bit float. Compressed formats - * such as unsigned 8-bit, uLaw, A-Law and MP3 are not support. If you need to load one of those - * files try setJavaSoundPreferred(true). Or convert it to a supported format using Audacity or Sox - * or some other sample file tool. Here is an example of loading a sample from a file. - * - *
- * 
- *     File sampleFile = new File("guitar.wav");
- *     FloatSample sample = SampleLoader.loadFloatSample( sampleFile );
- * 
- * 
- * - * @author Phil Burk (C) 2011 Mobileer Inc - */ -public class SampleLoader { - private static boolean javaSoundPreferred = false; - private static final String JS_LOADER_NAME = "com.jsyn.util.JavaSoundSampleLoader"; - - /** - * Try to create an implementation of AudioSampleLoader. - * - * @return A device supported on this platform. - */ - private static AudioSampleLoader createLoader() { - AudioSampleLoader loader = null; - try { - if (javaSoundPreferred) { - loader = (AudioSampleLoader) JavaTools.loadClass(JS_LOADER_NAME).newInstance(); - } else { - loader = new CustomSampleLoader(); - } - } catch (InstantiationException e) { - e.printStackTrace(); - } catch (IllegalAccessException e) { - e.printStackTrace(); - } - return loader; - } - - /** - * Load a FloatSample from a File object. - */ - public static FloatSample loadFloatSample(File fileIn) throws IOException { - AudioSampleLoader loader = SampleLoader.createLoader(); - return loader.loadFloatSample(fileIn); - } - - /** - * Load a FloatSample from an InputStream. This is handy when loading Resources from a JAR file. - */ - public static FloatSample loadFloatSample(InputStream inputStream) throws IOException { - AudioSampleLoader loader = SampleLoader.createLoader(); - return loader.loadFloatSample(inputStream); - } - - /** - * Load a FloatSample from a URL.. This is handy when loading Resources from a website. - */ - public static FloatSample loadFloatSample(URL url) throws IOException { - AudioSampleLoader loader = SampleLoader.createLoader(); - return loader.loadFloatSample(url); - } - - public static boolean isJavaSoundPreferred() { - return javaSoundPreferred; - } - - /** - * If set true then the audio file parser from JavaSound will be used. Note that JavaSound - * cannot load audio files containing floating point data. But it may be able to load some - * compressed data formats such as uLaw. - * - * Note: JavaSound is not supported on Android. - * - * @param javaSoundPreferred - */ - public static void setJavaSoundPreferred(boolean javaSoundPreferred) { - SampleLoader.javaSoundPreferred = javaSoundPreferred; - } - - /** - * Decode 24 bit samples from a BigEndian byte array into a float array. The samples will be - * normalized into the range -1.0 to +1.0. - * - * @param audioBytes raw data from an audio file - * @param offset first element of byte array - * @param numBytes number of bytes to process - * @param data array to be filled with floats - * @param outputOffset first element of float array to be filled - */ - public static void decodeBigI24ToF32(byte[] audioBytes, int offset, int numBytes, float[] data, - int outputOffset) { - int lastByte = offset + numBytes; - int byteCursor = offset; - int floatCursor = outputOffset; - while (byteCursor < lastByte) { - int hi = ((audioBytes[byteCursor++]) & 0x00FF); - int mid = ((audioBytes[byteCursor++]) & 0x00FF); - int lo = ((audioBytes[byteCursor++]) & 0x00FF); - int value = (hi << 24) | (mid << 16) | (lo << 8); - data[floatCursor++] = value * (1.0f / Integer.MAX_VALUE); - } - } - - public static void decodeBigI16ToF32(byte[] audioBytes, int offset, int numBytes, float[] data, - int outputOffset) { - int lastByte = offset + numBytes; - int byteCursor = offset; - int floatCursor = outputOffset; - while (byteCursor < lastByte) { - int hi = ((audioBytes[byteCursor++]) & 0x00FF); - int lo = ((audioBytes[byteCursor++]) & 0x00FF); - short value = (short) ((hi << 8) | lo); - data[floatCursor++] = value * (1.0f / 32768); - } - } - - public static void decodeBigF32ToF32(byte[] audioBytes, int offset, int numBytes, float[] data, - int outputOffset) { - int lastByte = offset + numBytes; - int byteCursor = offset; - int floatCursor = outputOffset; - while (byteCursor < lastByte) { - int bits = audioBytes[byteCursor++]; - bits = (bits << 8) | ((audioBytes[byteCursor++]) & 0x00FF); - bits = (bits << 8) | ((audioBytes[byteCursor++]) & 0x00FF); - bits = (bits << 8) | ((audioBytes[byteCursor++]) & 0x00FF); - data[floatCursor++] = Float.intBitsToFloat(bits); - } - } - - public static void decodeBigI32ToF32(byte[] audioBytes, int offset, int numBytes, float[] data, - int outputOffset) { - int lastByte = offset + numBytes; - int byteCursor = offset; - int floatCursor = outputOffset; - while (byteCursor < lastByte) { - int value = audioBytes[byteCursor++]; // MSB - value = (value << 8) | ((audioBytes[byteCursor++]) & 0x00FF); - value = (value << 8) | ((audioBytes[byteCursor++]) & 0x00FF); - value = (value << 8) | ((audioBytes[byteCursor++]) & 0x00FF); - data[floatCursor++] = value * (1.0f / Integer.MAX_VALUE); - } - } - - public static void decodeLittleF32ToF32(byte[] audioBytes, int offset, int numBytes, - float[] data, int outputOffset) { - int lastByte = offset + numBytes; - int byteCursor = offset; - int floatCursor = outputOffset; - while (byteCursor < lastByte) { - int bits = ((audioBytes[byteCursor++]) & 0x00FF); // LSB - bits += ((audioBytes[byteCursor++]) & 0x00FF) << 8; - bits += ((audioBytes[byteCursor++]) & 0x00FF) << 16; - bits += (audioBytes[byteCursor++]) << 24; - data[floatCursor++] = Float.intBitsToFloat(bits); - } - } - - public static void decodeLittleI32ToF32(byte[] audioBytes, int offset, int numBytes, - float[] data, int outputOffset) { - int lastByte = offset + numBytes; - int byteCursor = offset; - int floatCursor = outputOffset; - while (byteCursor < lastByte) { - int value = ((audioBytes[byteCursor++]) & 0x00FF); - value += ((audioBytes[byteCursor++]) & 0x00FF) << 8; - value += ((audioBytes[byteCursor++]) & 0x00FF) << 16; - value += (audioBytes[byteCursor++]) << 24; - data[floatCursor++] = value * (1.0f / Integer.MAX_VALUE); - } - } - - public static void decodeLittleI24ToF32(byte[] audioBytes, int offset, int numBytes, - float[] data, int outputOffset) { - int lastByte = offset + numBytes; - int byteCursor = offset; - int floatCursor = outputOffset; - while (byteCursor < lastByte) { - int lo = ((audioBytes[byteCursor++]) & 0x00FF); - int mid = ((audioBytes[byteCursor++]) & 0x00FF); - int hi = ((audioBytes[byteCursor++]) & 0x00FF); - int value = (hi << 24) | (mid << 16) | (lo << 8); - data[floatCursor++] = value * (1.0f / Integer.MAX_VALUE); - } - } - - public static void decodeLittleI16ToF32(byte[] audioBytes, int offset, int numBytes, - float[] data, int outputOffset) { - int lastByte = offset + numBytes; - int byteCursor = offset; - int floatCursor = outputOffset; - while (byteCursor < lastByte) { - int lo = ((audioBytes[byteCursor++]) & 0x00FF); - int hi = ((audioBytes[byteCursor++]) & 0x00FF); - short value = (short) ((hi << 8) | lo); - float sample = value * (1.0f / 32768); - data[floatCursor++] = sample; - } - } - -} diff --git a/src/com/jsyn/util/SignalCorrelator.java b/src/com/jsyn/util/SignalCorrelator.java deleted file mode 100644 index ebdd46b..0000000 --- a/src/com/jsyn/util/SignalCorrelator.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2011 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.util; - -/** - * Interface used to evaluate various algorithms for pitch detection. - * - * @author Phil Burk (C) 2011 Mobileer Inc - */ -public interface SignalCorrelator { - /** - * Add a sample to be analyzed. The samples will generally be held in a circular buffer. - * - * @param value - * @return true if a new period value has been generated - */ - public boolean addSample(double value); - - /** - * @return the estimated period of the waveform in samples - */ - public double getPeriod(); - - /** - * Measure of how confident the analyzer is of the last result. - * - * @return quality of the estimate between 0.0 and 1.0 - */ - public double getConfidence(); - - /** For internal debugging. */ - public float[] getDiffs(); - -} diff --git a/src/com/jsyn/util/StreamingThread.java b/src/com/jsyn/util/StreamingThread.java deleted file mode 100644 index 682f476..0000000 --- a/src/com/jsyn/util/StreamingThread.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright 2011 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.util; - -import java.io.IOException; - -import com.jsyn.io.AudioInputStream; -import com.jsyn.io.AudioOutputStream; - -/** - * Read from an AudioInputStream and write to an AudioOutputStream as a background thread. - * - * @author Phil Burk (C) 2011 Mobileer Inc - */ -public class StreamingThread extends Thread { - private AudioInputStream inputStream; - private AudioOutputStream outputStream; - private int framesPerBuffer = 1024; - private volatile boolean go = true; - private TransportModel transportModel; - private long framePosition; - private long maxFrames; - private int samplesPerFrame = 1; - - public StreamingThread(AudioInputStream inputStream, AudioOutputStream outputStream) { - this.inputStream = inputStream; - this.outputStream = outputStream; - } - - @Override - public void run() { - double[] buffer = new double[framesPerBuffer * samplesPerFrame]; - try { - transportModel.firePositionChanged(framePosition); - transportModel.fireStateChanged(TransportModel.STATE_RUNNING); - int framesToRead = geteFramesToRead(buffer); - while (go && (framesToRead > 0)) { - int samplesToRead = framesToRead * samplesPerFrame; - while (samplesToRead > 0) { - int samplesRead = inputStream.read(buffer, 0, samplesToRead); - outputStream.write(buffer, 0, samplesRead); - samplesToRead -= samplesRead; - } - framePosition += framesToRead; - transportModel.firePositionChanged(framePosition); - framesToRead = geteFramesToRead(buffer); - } - transportModel.fireStateChanged(TransportModel.STATE_STOPPED); - } catch (IOException e) { - e.printStackTrace(); - } - } - - private int geteFramesToRead(double[] buffer) { - if (maxFrames > 0) { - long numToRead = maxFrames - framePosition; - if (numToRead < 0) { - return 0; - } else if (numToRead > framesPerBuffer) { - numToRead = framesPerBuffer; - } - return (int) numToRead; - } else { - return framesPerBuffer; - } - } - - public int getFramesPerBuffer() { - return framesPerBuffer; - } - - /** - * Only call this before the thread has started. - * - * @param framesPerBuffer - */ - public void setFramesPerBuffer(int framesPerBuffer) { - this.framesPerBuffer = framesPerBuffer; - } - - public void requestStop() { - go = false; - } - - public TransportModel getTransportModel() { - return transportModel; - } - - public void setTransportModel(TransportModel transportModel) { - this.transportModel = transportModel; - } - - /** - * @param maxFrames - */ - public void setMaxFrames(long maxFrames) { - this.maxFrames = maxFrames; - } - - public int getSamplesPerFrame() { - return samplesPerFrame; - } - - public void setSamplesPerFrame(int samplesPerFrame) { - this.samplesPerFrame = samplesPerFrame; - } -} diff --git a/src/com/jsyn/util/TransportListener.java b/src/com/jsyn/util/TransportListener.java deleted file mode 100644 index 3c8b048..0000000 --- a/src/com/jsyn/util/TransportListener.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * 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.util; - -public interface TransportListener { - /** - * @param transportModel - * @param framePosition position in frames - */ - void positionChanged(TransportModel transportModel, long framePosition); - - /** - * @param transportModel - * @param state for example TransportModel.STATE_STOPPED - */ - void stateChanged(TransportModel transportModel, int state); -} diff --git a/src/com/jsyn/util/TransportModel.java b/src/com/jsyn/util/TransportModel.java deleted file mode 100644 index bcc75be..0000000 --- a/src/com/jsyn/util/TransportModel.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * 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.util; - -import java.util.concurrent.CopyOnWriteArrayList; - -public class TransportModel { - public static final int STATE_STOPPED = 0; - public static final int STATE_PAUSED = 1; - public static final int STATE_RUNNING = 2; - - private CopyOnWriteArrayList listeners = new CopyOnWriteArrayList(); - private int state = STATE_STOPPED; - private long position; - - public void addTransportListener(TransportListener listener) { - listeners.add(listener); - } - - public void removeTransportListener(TransportListener listener) { - listeners.remove(listener); - } - - public void setState(int newState) { - state = newState; - fireStateChanged(newState); - } - - public int getState() { - return state; - } - - public void setPosition(long newPosition) { - position = newPosition; - firePositionChanged(newPosition); - } - - public long getPosition() { - return position; - } - - public void fireStateChanged(int newState) { - for (TransportListener listener : listeners) { - listener.stateChanged(this, newState); - } - } - - public void firePositionChanged(long newPosition) { - for (TransportListener listener : listeners) { - listener.positionChanged(this, newPosition); - } - } -} diff --git a/src/com/jsyn/util/VoiceAllocator.java b/src/com/jsyn/util/VoiceAllocator.java deleted file mode 100644 index f20f7a5..0000000 --- a/src/com/jsyn/util/VoiceAllocator.java +++ /dev/null @@ -1,258 +0,0 @@ -/* - * Copyright 2011 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.util; - -import com.jsyn.Synthesizer; -import com.jsyn.unitgen.UnitVoice; -import com.softsynth.shared.time.ScheduledCommand; -import com.softsynth.shared.time.TimeStamp; - -/** - * Allocate voices based on an integer tag. The tag could, for example, be a MIDI note number. Or a - * tag could be an int that always increments. Use the same tag to refer to a voice for noteOn() and - * noteOff(). If no new voices are available then a voice in use will be stolen. - * - * @author Phil Burk (C) 2011 Mobileer Inc - */ -public class VoiceAllocator implements Instrument { - private int maxVoices; - private VoiceTracker[] trackers; - private long tick; - private Synthesizer synthesizer; - private static final int UNASSIGNED_PRESET = -1; - private int mPresetIndex = UNASSIGNED_PRESET; - - /** - * Create an allocator for the array of UnitVoices. The array must be full of instantiated - * UnitVoices that are connected to some kind of mixer. - * - * @param voices - */ - public VoiceAllocator(UnitVoice[] voices) { - maxVoices = voices.length; - trackers = new VoiceTracker[maxVoices]; - for (int i = 0; i < maxVoices; i++) { - trackers[i] = new VoiceTracker(); - trackers[i].voice = voices[i]; - } - } - - public Synthesizer getSynthesizer() { - if (synthesizer == null) { - synthesizer = trackers[0].voice.getUnitGenerator().getSynthesizer(); - } - return synthesizer; - } - - private class VoiceTracker { - UnitVoice voice; - int tag = -1; - int presetIndex = UNASSIGNED_PRESET; - long when; - boolean on; - - public void off() { - on = false; - when = tick++; - } - } - - /** - * @return number of UnitVoices passed to the allocator. - */ - public int getVoiceCount() { - return maxVoices; - } - - private VoiceTracker findVoice(int tag) { - for (VoiceTracker tracker : trackers) { - if (tracker.tag == tag) { - return tracker; - } - } - return null; - } - - private VoiceTracker stealVoice() { - VoiceTracker bestOff = null; - VoiceTracker bestOn = null; - for (VoiceTracker tracker : trackers) { - if (tracker.voice == null) { - return tracker; - } - // If we have a bestOff voice then don't even bother with on voices. - else if (bestOff != null) { - // Older off voice? - if (!tracker.on && (tracker.when < bestOff.when)) { - bestOff = tracker; - } - } else if (tracker.on) { - if (bestOn == null) { - bestOn = tracker; - } else if (tracker.when < bestOn.when) { - bestOn = tracker; - } - } else { - bestOff = tracker; - } - } - if (bestOff != null) { - return bestOff; - } else { - return bestOn; - } - } - - /** - * Allocate a Voice associated with this tag. It will first pick a voice already assigned to - * that tag. Next it will pick the oldest voice that is off. Next it will pick the oldest voice - * that is on. If you are using timestamps to play the voice in the future then you should use - * the noteOn() noteOff() and setPort() methods. - * - * @param tag - * @return Voice that is most available. - */ - protected synchronized UnitVoice allocate(int tag) { - VoiceTracker tracker = allocateTracker(tag); - return tracker.voice; - } - - private VoiceTracker allocateTracker(int tag) { - VoiceTracker tracker = findVoice(tag); - if (tracker == null) { - tracker = stealVoice(); - } - tracker.tag = tag; - tracker.when = tick++; - tracker.on = true; - return tracker; - } - - protected synchronized boolean isOn(int tag) { - VoiceTracker tracker = findVoice(tag); - if (tracker != null) { - return tracker.on; - } - return false; - } - - protected synchronized UnitVoice off(int tag) { - VoiceTracker tracker = findVoice(tag); - if (tracker != null) { - tracker.off(); - return tracker.voice; - } - return null; - } - - /** Turn off all the note currently on. */ - @Override - public void allNotesOff(TimeStamp timeStamp) { - getSynthesizer().scheduleCommand(timeStamp, new ScheduledCommand() { - @Override - public void run() { - for (VoiceTracker tracker : trackers) { - if (tracker.on) { - tracker.voice.noteOff(getSynthesizer().createTimeStamp()); - tracker.off(); - } - } - } - }); - } - - /** - * Play a note on the voice and associate it with the given tag. if needed a new voice will be - * allocated and an old voice may be turned off. - */ - @Override - public void noteOn(final int tag, final double frequency, final double amplitude, - TimeStamp timeStamp) { - getSynthesizer().scheduleCommand(timeStamp, new ScheduledCommand() { - @Override - public void run() { - VoiceTracker voiceTracker = allocateTracker(tag); - if (voiceTracker.presetIndex != mPresetIndex) { - voiceTracker.voice.usePreset(mPresetIndex); - voiceTracker.presetIndex = mPresetIndex; - } - voiceTracker.voice.noteOn(frequency, amplitude, getSynthesizer().createTimeStamp()); - } - }); - } - - /** - * Play a note on the voice and associate it with the given tag. if needed a new voice will be - * allocated and an old voice may be turned off. - * Apply an operation to the voice. - */ - public void noteOn(final int tag, - final double frequency, - final double amplitude, - final VoiceOperation operation, - TimeStamp timeStamp) { - getSynthesizer().scheduleCommand(timeStamp, new ScheduledCommand() { - @Override - public void run() { - VoiceTracker voiceTracker = allocateTracker(tag); - operation.operate(voiceTracker.voice); - voiceTracker.voice.noteOn(frequency, amplitude, getSynthesizer().createTimeStamp()); - } - }); - } - - /** Turn off the voice associated with the given tag if allocated. */ - @Override - public void noteOff(final int tag, TimeStamp timeStamp) { - getSynthesizer().scheduleCommand(timeStamp, new ScheduledCommand() { - @Override - public void run() { - VoiceTracker voiceTracker = findVoice(tag); - if (voiceTracker != null) { - voiceTracker.voice.noteOff(getSynthesizer().createTimeStamp()); - off(tag); - } - } - }); - } - - /** Set a port on the voice associated with the given tag if allocated. */ - @Override - public void setPort(final int tag, final String portName, final double value, - TimeStamp timeStamp) { - getSynthesizer().scheduleCommand(timeStamp, new ScheduledCommand() { - @Override - public void run() { - VoiceTracker voiceTracker = findVoice(tag); - if (voiceTracker != null) { - voiceTracker.voice.setPort(portName, value, getSynthesizer().createTimeStamp()); - } - } - }); - } - - @Override - public void usePreset(final int presetIndex, TimeStamp timeStamp) { - getSynthesizer().scheduleCommand(timeStamp, new ScheduledCommand() { - @Override - public void run() { - mPresetIndex = presetIndex; - } - }); - } - -} diff --git a/src/com/jsyn/util/VoiceDescription.java b/src/com/jsyn/util/VoiceDescription.java deleted file mode 100644 index b7be044..0000000 --- a/src/com/jsyn/util/VoiceDescription.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright 2011 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.util; - -import com.jsyn.unitgen.UnitVoice; - -/** - * Describe a voice so that a user can pick it out of an InstrumentLibrary. - * - * @author Phil Burk (C) 2011 Mobileer Inc - * @see PolyphonicInstrument - */ -public abstract class VoiceDescription { - private String name; - private String[] presetNames; - - public VoiceDescription(String name, String[] presetNames) { - this.name = name; - this.presetNames = presetNames; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public int getPresetCount() { - return presetNames.length; - } - - public String[] getPresetNames() { - return presetNames; - } - - public abstract String[] getTags(int presetIndex); - - /** - * Instantiate one of these voices. You may want to call usePreset(n) on the voice after - * instantiating it. - * - * @return a voice - */ - public abstract UnitVoice createUnitVoice(); - - public abstract String getVoiceClassName(); - - @Override - public String toString() { - return name + "[" + getPresetCount() + "]"; - } -} diff --git a/src/com/jsyn/util/VoiceOperation.java b/src/com/jsyn/util/VoiceOperation.java deleted file mode 100644 index cd3b48e..0000000 --- a/src/com/jsyn/util/VoiceOperation.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.jsyn.util; - -import com.jsyn.unitgen.UnitVoice; - -public interface VoiceOperation { - public void operate(UnitVoice voice); -} diff --git a/src/com/jsyn/util/WaveFileWriter.java b/src/com/jsyn/util/WaveFileWriter.java deleted file mode 100644 index 32e9995..0000000 --- a/src/com/jsyn/util/WaveFileWriter.java +++ /dev/null @@ -1,293 +0,0 @@ -/* - * Copyright 2011 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.util; - -import java.io.BufferedOutputStream; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.io.RandomAccessFile; - -import com.jsyn.io.AudioOutputStream; - -/** - * Write audio data to a WAV file. - * - *
- * 
- * WaveFileWriter writer = new WaveFileWriter(file);
- * writer.setFrameRate(22050);
- * writer.setBitsPerSample(24);
- * writer.write(floatArray);
- * writer.close();
- * 
- * 
- * - * @author Phil Burk (C) 2011 Mobileer Inc - */ -public class WaveFileWriter implements AudioOutputStream { - private static final short WAVE_FORMAT_PCM = 1; - private OutputStream outputStream; - private long riffSizePosition = 0; - private long dataSizePosition = 0; - private int frameRate = 44100; - private int samplesPerFrame = 1; - private int bitsPerSample = 16; - private int bytesWritten; - private File outputFile; - private boolean headerWritten = false; - private final static int PCM24_MIN = -(1 << 23); - private final static int PCM24_MAX = (1 << 23) - 1; - - /** - * Create a writer that will write to the specified file. - * - * @param outputFile - * @throws FileNotFoundException - */ - public WaveFileWriter(File outputFile) throws FileNotFoundException { - this.outputFile = outputFile; - FileOutputStream fileOut = new FileOutputStream(outputFile); - outputStream = new BufferedOutputStream(fileOut); - } - - /** - * @param frameRate default is 44100 - */ - public void setFrameRate(int frameRate) { - this.frameRate = frameRate; - } - - public int getFrameRate() { - return frameRate; - } - - /** For stereo, set this to 2. Default is 1. */ - public void setSamplesPerFrame(int samplesPerFrame) { - this.samplesPerFrame = samplesPerFrame; - } - - public int getSamplesPerFrame() { - return samplesPerFrame; - } - - /** Only 16 or 24 bit samples supported at the moment. Default is 16. */ - public void setBitsPerSample(int bits) { - if ((bits != 16) && (bits != 24)) { - throw new IllegalArgumentException("Only 16 or 24 bits per sample allowed. Not " + bits); - } - bitsPerSample = bits; - } - - public int getBitsPerSample() { - return bitsPerSample; - } - - @Override - public void close() throws IOException { - outputStream.close(); - fixSizes(); - } - - /** Write entire buffer of audio samples to the WAV file. */ - @Override - public void write(double[] buffer) throws IOException { - write(buffer, 0, buffer.length); - } - - /** Write audio to the WAV file. */ - public void write(float[] buffer) throws IOException { - write(buffer, 0, buffer.length); - } - - /** Write single audio data value to the WAV file. */ - @Override - public void write(double value) throws IOException { - if (!headerWritten) { - writeHeader(); - } - - if (bitsPerSample == 24) { - writePCM24(value); - } else { - writePCM16(value); - } - } - - private void writePCM24(double value) throws IOException { - // Offset before casting so that we can avoid using floor(). - // Also round by adding 0.5 so that very small signals go to zero. - double temp = (PCM24_MAX * value) + 0.5 - PCM24_MIN; - int sample = ((int) temp) + PCM24_MIN; - // clip to 24-bit range - if (sample > PCM24_MAX) { - sample = PCM24_MAX; - } else if (sample < PCM24_MIN) { - sample = PCM24_MIN; - } - // encode as little-endian - writeByte(sample); // little end - writeByte(sample >> 8); // middle - writeByte(sample >> 16); // big end - } - - private void writePCM16(double value) throws IOException { - // Offset before casting so that we can avoid using floor(). - // Also round by adding 0.5 so that very small signals go to zero. - double temp = (Short.MAX_VALUE * value) + 0.5 - Short.MIN_VALUE; - int sample = ((int) temp) + Short.MIN_VALUE; - if (sample > Short.MAX_VALUE) { - sample = Short.MAX_VALUE; - } else if (sample < Short.MIN_VALUE) { - sample = Short.MIN_VALUE; - } - writeByte(sample); // little end - writeByte(sample >> 8); // big end - } - - /** Write audio to the WAV file. */ - @Override - public void write(double[] buffer, int start, int count) throws IOException { - for (int i = 0; i < count; i++) { - write(buffer[start + i]); - } - } - - /** Write audio to the WAV file. */ - public void write(float[] buffer, int start, int count) throws IOException { - for (int i = 0; i < count; i++) { - write(buffer[start + i]); - } - } - - // Write lower 8 bits. Upper bits ignored. - private void writeByte(int b) throws IOException { - outputStream.write(b); - bytesWritten += 1; - } - - /** - * Write a 32 bit integer to the stream in Little Endian format. - */ - public void writeIntLittle(int n) throws IOException { - writeByte(n); - writeByte(n >> 8); - writeByte(n >> 16); - writeByte(n >> 24); - } - - /** - * Write a 16 bit integer to the stream in Little Endian format. - */ - public void writeShortLittle(short n) throws IOException { - writeByte(n); - writeByte(n >> 8); - } - - /** - * Write a simple WAV header for PCM data. - */ - private void writeHeader() throws IOException { - writeRiffHeader(); - writeFormatChunk(); - writeDataChunkHeader(); - outputStream.flush(); - headerWritten = true; - } - - /** - * Write a 'RIFF' file header and a 'WAVE' ID to the WAV file. - */ - private void writeRiffHeader() throws IOException { - writeByte('R'); - writeByte('I'); - writeByte('F'); - writeByte('F'); - riffSizePosition = bytesWritten; - writeIntLittle(Integer.MAX_VALUE); - writeByte('W'); - writeByte('A'); - writeByte('V'); - writeByte('E'); - } - - /** - * Write an 'fmt ' chunk to the WAV file containing the given information. - */ - public void writeFormatChunk() throws IOException { - int bytesPerSample = (bitsPerSample + 7) / 8; - - writeByte('f'); - writeByte('m'); - writeByte('t'); - writeByte(' '); - writeIntLittle(16); // chunk size - writeShortLittle(WAVE_FORMAT_PCM); - writeShortLittle((short) samplesPerFrame); - writeIntLittle(frameRate); - // bytes/second - writeIntLittle(frameRate * samplesPerFrame * bytesPerSample); - // block align - writeShortLittle((short) (samplesPerFrame * bytesPerSample)); - writeShortLittle((short) bitsPerSample); - } - - /** - * Write a 'data' chunk header to the WAV file. This should be followed by call to - * writeShortLittle() to write the data to the chunk. - */ - public void writeDataChunkHeader() throws IOException { - writeByte('d'); - writeByte('a'); - writeByte('t'); - writeByte('a'); - dataSizePosition = bytesWritten; - writeIntLittle(Integer.MAX_VALUE); // size - } - - /** - * Fix RIFF and data chunk sizes based on final size. Assume data chunk is the last chunk. - */ - private void fixSizes() throws IOException { - RandomAccessFile randomFile = new RandomAccessFile(outputFile, "rw"); - try { - // adjust RIFF size - long end = bytesWritten; - int riffSize = (int) (end - riffSizePosition) - 4; - randomFile.seek(riffSizePosition); - writeRandomIntLittle(randomFile, riffSize); - // adjust data size - int dataSize = (int) (end - dataSizePosition) - 4; - randomFile.seek(dataSizePosition); - writeRandomIntLittle(randomFile, dataSize); - } finally { - randomFile.close(); - } - } - - private void writeRandomIntLittle(RandomAccessFile randomFile, int n) throws IOException { - byte[] buffer = new byte[4]; - buffer[0] = (byte) n; - buffer[1] = (byte) (n >> 8); - buffer[2] = (byte) (n >> 16); - buffer[3] = (byte) (n >> 24); - randomFile.write(buffer); - } - -} diff --git a/src/com/jsyn/util/WaveRecorder.java b/src/com/jsyn/util/WaveRecorder.java deleted file mode 100644 index 059863b..0000000 --- a/src/com/jsyn/util/WaveRecorder.java +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright 2011 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.util; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; - -import com.jsyn.Synthesizer; -import com.jsyn.ports.UnitInputPort; - -/** - * Connect a unit generator to the input. Then start() recording. The signal will be written to a - * WAV format file that can be read by other programs. - * - * @author Phil Burk (C) 2011 Mobileer Inc - */ -public class WaveRecorder { - private AudioStreamReader reader; - private WaveFileWriter writer; - private StreamingThread thread; - private Synthesizer synth; - private TransportModel transportModel = new TransportModel(); - private double maxRecordingTime; - - /** - * Create a stereo 16-bit recorder. - * - * @param synth - * @param outputFile - * @throws FileNotFoundException - */ - public WaveRecorder(Synthesizer synth, File outputFile) throws FileNotFoundException { - this(synth, outputFile, 2, 16); - } - - public WaveRecorder(Synthesizer synth, File outputFile, int samplesPerFrame) - throws FileNotFoundException { - this(synth, outputFile, samplesPerFrame, 16); - } - - /** - * @param synth - * @param outputFile - * @param samplesPerFrame 1 for mono, 2 for stereo - * @param bitsPerSample 16 or 24 - * @throws FileNotFoundException - */ - public WaveRecorder(Synthesizer synth, File outputFile, int samplesPerFrame, int bitsPerSample) - throws FileNotFoundException { - this.synth = synth; - reader = new AudioStreamReader(synth, samplesPerFrame); - - writer = new WaveFileWriter(outputFile); - writer.setFrameRate(synth.getFrameRate()); - writer.setSamplesPerFrame(samplesPerFrame); - writer.setBitsPerSample(bitsPerSample); - } - - public UnitInputPort getInput() { - return reader.getInput(); - } - - public void start() { - stop(); - thread = new StreamingThread(reader, writer); - thread.setTransportModel(transportModel); - thread.setSamplesPerFrame(writer.getSamplesPerFrame()); - updateMaxRecordingTime(); - thread.start(); - } - - public void stop() { - if (thread != null) { - thread.requestStop(); - try { - thread.join(500); - } catch (InterruptedException e) { - } - thread = null; - } - } - - /** Close and disconnect any connected inputs. */ - public void close() throws IOException { - stop(); - if (writer != null) { - writer.close(); - writer = null; - } - if (reader != null) { - reader.close(); - for (int i = 0; i < reader.getInput().getNumParts(); i++) { - reader.getInput().disconnectAll(i); - } - reader = null; - } - } - - public void addTransportListener(TransportListener listener) { - transportModel.addTransportListener(listener); - } - - public void removeTransportListener(TransportListener listener) { - transportModel.removeTransportListener(listener); - } - - public void setMaxRecordingTime(double maxRecordingTime) { - this.maxRecordingTime = maxRecordingTime; - updateMaxRecordingTime(); - } - - private void updateMaxRecordingTime() { - StreamingThread streamingThread = thread; - if (streamingThread != null) { - long maxFrames = (long) (maxRecordingTime * synth.getFrameRate()); - streamingThread.setMaxFrames(maxFrames); - } - } -} diff --git a/src/com/jsyn/util/soundfile/AIFFFileParser.java b/src/com/jsyn/util/soundfile/AIFFFileParser.java deleted file mode 100644 index 2b09d78..0000000 --- a/src/com/jsyn/util/soundfile/AIFFFileParser.java +++ /dev/null @@ -1,227 +0,0 @@ -/* - * 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.util.soundfile; - -import java.io.EOFException; -import java.io.IOException; - -import com.jsyn.data.FloatSample; -import com.jsyn.data.SampleMarker; -import com.jsyn.util.SampleLoader; - -public class AIFFFileParser extends AudioFileParser { - private static final String SUPPORTED_FORMATS = "Only 16 and 24 bit PCM or 32-bit float AIF files supported."; - static final int AIFF_ID = ('A' << 24) | ('I' << 16) | ('F' << 8) | 'F'; - static final int AIFC_ID = ('A' << 24) | ('I' << 16) | ('F' << 8) | 'C'; - static final int COMM_ID = ('C' << 24) | ('O' << 16) | ('M' << 8) | 'M'; - static final int SSND_ID = ('S' << 24) | ('S' << 16) | ('N' << 8) | 'D'; - static final int MARK_ID = ('M' << 24) | ('A' << 16) | ('R' << 8) | 'K'; - static final int INST_ID = ('I' << 24) | ('N' << 16) | ('S' << 8) | 'T'; - static final int NONE_ID = ('N' << 24) | ('O' << 16) | ('N' << 8) | 'E'; - static final int FL32_ID = ('F' << 24) | ('L' << 16) | ('3' << 8) | '2'; - static final int FL32_ID_LC = ('f' << 24) | ('l' << 16) | ('3' << 8) | '2'; - - int sustainBeginID = -1; - int sustainEndID = -1; - int releaseBeginID = -1; - int releaseEndID = -1; - boolean typeFloat = false; - - @Override - FloatSample finish() throws IOException { - setLoops(); - - if ((byteData == null)) { - throw new IOException("No data found in audio sample."); - } - float[] floatData = new float[numFrames * samplesPerFrame]; - if (bitsPerSample == 16) { - SampleLoader.decodeBigI16ToF32(byteData, 0, byteData.length, floatData, 0); - } else if (bitsPerSample == 24) { - SampleLoader.decodeBigI24ToF32(byteData, 0, byteData.length, floatData, 0); - } else if (bitsPerSample == 32) { - if (typeFloat) { - SampleLoader.decodeBigF32ToF32(byteData, 0, byteData.length, floatData, 0); - } else { - SampleLoader.decodeBigI32ToF32(byteData, 0, byteData.length, floatData, 0); - } - } else { - throw new IOException(SUPPORTED_FORMATS + " size = " + bitsPerSample); - } - - return makeSample(floatData); - } - - double read80BitFloat() throws IOException { - /* - * This is not a full decoding of the 80 bit number but it should suffice for the range we - * expect. - */ - byte[] bytes = new byte[10]; - parser.read(bytes); - int exp = ((bytes[0] & 0x3F) << 8) | (bytes[1] & 0xFF); - int mant = ((bytes[2] & 0xFF) << 16) | ((bytes[3] & 0xFF) << 8) | (bytes[4] & 0xFF); - // System.out.println( "exp = " + exp + ", mant = " + mant ); - return mant / (double) (1 << (22 - exp)); - } - - void parseCOMMChunk(IFFParser parser, int ckSize) throws IOException { - samplesPerFrame = parser.readShortBig(); - numFrames = parser.readIntBig(); - bitsPerSample = parser.readShortBig(); - frameRate = read80BitFloat(); - if (ckSize > 18) { - int format = parser.readIntBig(); - // Validate data format. - if ((format == FL32_ID) || (format == FL32_ID_LC)) { - typeFloat = true; - } else if (format == NONE_ID) { - typeFloat = false; - } else { - throw new IOException(SUPPORTED_FORMATS + " format " + IFFParser.IDToString(format)); - } - } - - bytesPerSample = (bitsPerSample + 7) / 8; - bytesPerFrame = bytesPerSample * samplesPerFrame; - } - - /* parse tuning and multi-sample info */ - @SuppressWarnings("unused") - void parseINSTChunk(IFFParser parser, int ckSize) throws IOException { - int baseNote = parser.readByte(); - int detune = parser.readByte(); - originalPitch = baseNote + (0.01 * detune); - - int lowNote = parser.readByte(); - int highNote = parser.readByte(); - - parser.skip(2); /* lo,hi velocity */ - int gain = parser.readShortBig(); - - int playMode = parser.readShortBig(); /* sustain */ - sustainBeginID = parser.readShortBig(); - sustainEndID = parser.readShortBig(); - - playMode = parser.readShortBig(); /* release */ - releaseBeginID = parser.readShortBig(); - releaseEndID = parser.readShortBig(); - } - - private void setLoops() { - SampleMarker cuePoint = cueMap.get(sustainBeginID); - if (cuePoint != null) { - sustainBegin = cuePoint.position; - } - cuePoint = cueMap.get(sustainEndID); - if (cuePoint != null) { - sustainEnd = cuePoint.position; - } - } - - void parseSSNDChunk(IFFParser parser, int ckSize) throws IOException { - long numRead; - // System.out.println("parseSSNDChunk()"); - int offset = parser.readIntBig(); - parser.readIntBig(); /* blocksize */ - parser.skip(offset); - dataPosition = parser.getOffset(); - int numBytes = ckSize - 8 - offset; - if (ifLoadData) { - byteData = new byte[numBytes]; - numRead = parser.read(byteData); - } else { - numRead = parser.skip(numBytes); - } - if (numRead != numBytes) - throw new EOFException("AIFF data chunk too short!"); - } - - void parseMARKChunk(IFFParser parser, int ckSize) throws IOException { - long startOffset = parser.getOffset(); - int numCuePoints = parser.readShortBig(); - // System.out.println( "parseCueChunk: numCuePoints = " + numCuePoints - // ); - for (int i = 0; i < numCuePoints; i++) { - // Some AIF files have a bogus numCuePoints so check to see if we - // are at end. - long numInMark = parser.getOffset() - startOffset; - if (numInMark >= ckSize) { - System.out.println("Reached end of MARK chunk with bogus numCuePoints = " - + numCuePoints); - break; - } - - int uniqueID = parser.readShortBig(); - int position = parser.readIntBig(); - int len = parser.read(); - String markerName = parseString(parser, len); - if ((len & 1) == 0) { - parser.skip(1); /* skip pad byte */ - } - - SampleMarker cuePoint = findOrCreateCuePoint(uniqueID); - cuePoint.position = position; - cuePoint.name = markerName; - - if (IFFParser.debug) { - System.out.println("AIFF Marker at " + position + ", " + markerName); - } - } - } - - /** - * Called by parse() method to handle FORM chunks in an AIFF specific manner. - * - * @param ckID four byte chunk ID such as 'data' - * @param ckSize size of chunk in bytes - * @exception IOException If parsing fails, or IO error occurs. - */ - @Override - public void handleForm(IFFParser parser, int ckID, int ckSize, int type) throws IOException { - if ((ckID == IFFParser.FORM_ID) && (type != AIFF_ID) && (type != AIFC_ID)) - throw new IOException("Bad AIFF form type = " + IFFParser.IDToString(type)); - } - - /** - * Called by parse() method to handle chunks in an AIFF specific manner. - * - * @param ckID four byte chunk ID such as 'data' - * @param ckSize size of chunk in bytes - * @exception IOException If parsing fails, or IO error occurs. - */ - @Override - public void handleChunk(IFFParser parser, int ckID, int ckSize) throws IOException { - switch (ckID) { - case COMM_ID: - parseCOMMChunk(parser, ckSize); - break; - case SSND_ID: - parseSSNDChunk(parser, ckSize); - break; - case MARK_ID: - parseMARKChunk(parser, ckSize); - break; - case INST_ID: - parseINSTChunk(parser, ckSize); - break; - default: - break; - } - } - -} diff --git a/src/com/jsyn/util/soundfile/AudioFileParser.java b/src/com/jsyn/util/soundfile/AudioFileParser.java deleted file mode 100644 index e7bb066..0000000 --- a/src/com/jsyn/util/soundfile/AudioFileParser.java +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright 2001 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.util.soundfile; - -import java.io.IOException; -import java.util.HashMap; - -import com.jsyn.data.FloatSample; -import com.jsyn.data.SampleMarker; - -/** - * Base class for various types of audio specific file parsers. - * - * @author (C) 2001 Phil Burk, SoftSynth.com - */ - -abstract class AudioFileParser implements ChunkHandler { - IFFParser parser; - protected byte[] byteData; - boolean ifLoadData = true; /* If true, load sound data into memory. */ - long dataPosition; /* - * Number of bytes from beginning of file where sound data resides. - */ - protected int bitsPerSample; - protected int bytesPerFrame; // in the file - protected int bytesPerSample; // in the file - protected HashMap cueMap = new HashMap(); - protected short samplesPerFrame; - protected double frameRate; - protected int numFrames; - protected double originalPitch = 60.0; - protected int sustainBegin = -1; - protected int sustainEnd = -1; - - public AudioFileParser() { - } - - /** - * @return Number of bytes from beginning of stream where sound data resides. - */ - public long getDataPosition() { - return dataPosition; - } - - /** - * This can be read by another thread when load()ing a sample to determine how many bytes have - * been read so far. - */ - public synchronized long getNumBytesRead() { - IFFParser p = parser; // prevent race - if (p != null) - return p.getOffset(); - else - return 0; - } - - /** - * This can be read by another thread when load()ing a sample to determine how many bytes need - * to be read. - */ - public synchronized long getFileSize() { - IFFParser p = parser; // prevent race - if (p != null) - return p.getFileSize(); - else - return 0; - } - - protected SampleMarker findOrCreateCuePoint(int uniqueID) { - SampleMarker cuePoint = cueMap.get(uniqueID); - if (cuePoint == null) { - cuePoint = new SampleMarker(); - cueMap.put(uniqueID, cuePoint); - } - return cuePoint; - } - - public FloatSample load(IFFParser parser) throws IOException { - this.parser = parser; - parser.parseAfterHead(this); - return finish(); - } - - abstract FloatSample finish() throws IOException; - - FloatSample makeSample(float[] floatData) throws IOException { - FloatSample floatSample = new FloatSample(floatData, samplesPerFrame); - - floatSample.setChannelsPerFrame(samplesPerFrame); - floatSample.setFrameRate(frameRate); - floatSample.setPitch(originalPitch); - - if (sustainBegin >= 0) { - floatSample.setSustainBegin(sustainBegin); - floatSample.setSustainEnd(sustainEnd); - } - - for (SampleMarker marker : cueMap.values()) { - floatSample.addMarker(marker); - } - - /* Set Sustain Loop by assuming first two markers are loop points. */ - if (floatSample.getMarkerCount() >= 2) { - floatSample.setSustainBegin(floatSample.getMarker(0).position); - floatSample.setSustainEnd(floatSample.getMarker(1).position); - } - return floatSample; - } - - protected String parseString(IFFParser parser, int textLength) throws IOException { - byte[] bar = new byte[textLength]; - parser.read(bar); - return new String(bar); - } -} diff --git a/src/com/jsyn/util/soundfile/ChunkHandler.java b/src/com/jsyn/util/soundfile/ChunkHandler.java deleted file mode 100644 index 6dfe26d..0000000 --- a/src/com/jsyn/util/soundfile/ChunkHandler.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 1997 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.util.soundfile; - -import java.io.IOException; - -/** - * Handle IFF Chunks as they are parsed from an IFF or RIFF file. - * - * @see IFFParser - * @see AudioSampleAIFF - * @author (C) 1997 Phil Burk, SoftSynth.com - */ -interface ChunkHandler { - /** - * The parser will call this when it encounters a FORM or LIST chunk that contains other chunks. - * This handler can either read the form's chunks, or let the parser find them and call - * handleChunk(). - * - * @param ID a 4 byte identifier such as FORM_ID that identifies the IFF chunk type. - * @param numBytes number of bytes contained in the FORM, not counting the FORM type. - * @param type a 4 byte identifier such as AIFF_ID that identifies the FORM type. - */ - public void handleForm(IFFParser parser, int ID, int numBytes, int type) throws IOException; - - /** - * The parser will call this when it encounters a chunk that is not a FORM or LIST. This handler - * can either read the chunk's, or ignore it. The parser will skip over any unread data. Do NOT - * read past the end of the chunk! - * - * @param ID a 4 byte identifier such as SSND_ID that identifies the IFF chunk type. - * @param numBytes number of bytes contained in the chunk, not counting the ID and size field. - */ - public void handleChunk(IFFParser parser, int ID, int numBytes) throws IOException; -} diff --git a/src/com/jsyn/util/soundfile/CustomSampleLoader.java b/src/com/jsyn/util/soundfile/CustomSampleLoader.java deleted file mode 100644 index 14efde9..0000000 --- a/src/com/jsyn/util/soundfile/CustomSampleLoader.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * 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.util.soundfile; - -import java.io.BufferedInputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.net.URL; - -import com.jsyn.data.FloatSample; -import com.jsyn.util.AudioSampleLoader; - -public class CustomSampleLoader implements AudioSampleLoader { - - @Override - public FloatSample loadFloatSample(File fileIn) throws IOException { - FileInputStream fileStream = new FileInputStream(fileIn); - BufferedInputStream inputStream = new BufferedInputStream(fileStream); - return loadFloatSample(inputStream); - } - - @Override - public FloatSample loadFloatSample(URL url) throws IOException { - InputStream rawStream = url.openStream(); - BufferedInputStream inputStream = new BufferedInputStream(rawStream); - return loadFloatSample(inputStream); - } - - @Override - public FloatSample loadFloatSample(InputStream inputStream) throws IOException { - AudioFileParser fileParser; - IFFParser parser = new IFFParser(inputStream); - parser.readHead(); - if (parser.isRIFF()) { - fileParser = new WAVEFileParser(); - } else if (parser.isIFF()) { - fileParser = new AIFFFileParser(); - } else { - throw new IOException("Unsupported audio file type."); - } - return fileParser.load(parser); - } - -} diff --git a/src/com/jsyn/util/soundfile/IFFParser.java b/src/com/jsyn/util/soundfile/IFFParser.java deleted file mode 100644 index f429657..0000000 --- a/src/com/jsyn/util/soundfile/IFFParser.java +++ /dev/null @@ -1,307 +0,0 @@ -/* - * Copyright 1997 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.util.soundfile; - -import java.io.EOFException; -import java.io.FilterInputStream; -import java.io.IOException; -import java.io.InputStream; - -/** - * Parse Electronic Arts style IFF File. IFF is a file format that allows "chunks" of data to be - * placed in a hierarchical file. It was designed by Jerry Morrison at Electronic Arts for the Amiga - * computer and is now used extensively by Apple Computer and other companies. IFF is an open - * standard. - * - * @see RIFFParser - * @see AudioSampleAIFF - * @author (C) 1997 Phil Burk, SoftSynth.com - */ - -class IFFParser extends FilterInputStream { - private long numBytesRead = 0; - private long totalSize = 0; - private int fileId; - static boolean debug = false; - - public static final int RIFF_ID = ('R' << 24) | ('I' << 16) | ('F' << 8) | 'F'; - public static final int LIST_ID = ('L' << 24) | ('I' << 16) | ('S' << 8) | 'T'; - public static final int FORM_ID = ('F' << 24) | ('O' << 16) | ('R' << 8) | 'M'; - - IFFParser(InputStream stream) { - super(stream); - numBytesRead = 0; - } - - /** - * Size of file based on outermost chunk size plus 8. Can be used to report progress when - * loading samples. - * - * @return Number of bytes in outer chunk plus header. - */ - public long getFileSize() { - return totalSize; - } - - /** - * Since IFF files use chunks with explicit size, it is important to keep track of how many - * bytes have been read from the file. Can be used to report progress when loading samples. - * - * @return Number of bytes read from stream, or skipped. - */ - public long getOffset() { - return numBytesRead; - } - - /** @return Next byte from stream. Increment offset by 1. */ - @Override - public int read() throws IOException { - numBytesRead++; - return super.read(); - } - - /** @return Next byte array from stream. Increment offset by len. */ - @Override - public int read(byte[] bar) throws IOException { - return read(bar, 0, bar.length); - } - - /** @return Next byte array from stream. Increment offset by len. */ - @Override - public int read(byte[] bar, int off, int len) throws IOException { - // Reading from a URL can return before all the bytes are available. - // So we keep reading until we get the whole thing. - int cursor = off; - int numLeft = len; - // keep reading data until we get it all - while (numLeft > 0) { - int numRead = super.read(bar, cursor, numLeft); - if (numRead < 0) - return numRead; - cursor += numRead; - numBytesRead += numRead; - numLeft -= numRead; - // System.out.println("read " + numRead + ", cursor = " + cursor + - // ", len = " + len); - } - return cursor - off; - } - - /** @return Skip forward in stream and add numBytes to offset. */ - @Override - public long skip(long numBytes) throws IOException { - numBytesRead += numBytes; - return super.skip(numBytes); - } - - /** Read 32 bit signed integer assuming Big Endian byte order. */ - public int readIntBig() throws IOException { - int result = read() & 0xFF; - result = (result << 8) | (read() & 0xFF); - result = (result << 8) | (read() & 0xFF); - int data = read(); - if (data == -1) - throw new EOFException("readIntBig() - EOF in middle of word at offset " + numBytesRead); - result = (result << 8) | (data & 0xFF); - return result; - } - - /** Read 32 bit signed integer assuming Little Endian byte order. */ - public int readIntLittle() throws IOException { - int result = read() & 0xFF; // LSB - result |= ((read() & 0xFF) << 8); - result |= ((read() & 0xFF) << 16); - int data = read(); - if (data == -1) - throw new EOFException("readIntLittle() - EOF in middle of word at offset " - + numBytesRead); - result |= (data << 24); - return result; - } - - /** Read 16 bit signed short assuming Big Endian byte order. */ - public short readShortBig() throws IOException { - short result = (short) ((read() << 8)); // MSB - int data = read(); - if (data == -1) - throw new EOFException("readShortBig() - EOF in middle of word at offset " - + numBytesRead); - result |= data & 0xFF; - return result; - } - - /** Read 16 bit signed short assuming Little Endian byte order. */ - public short readShortLittle() throws IOException { - short result = (short) (read() & 0xFF); // LSB - int data = read(); // MSB - if (data == -1) - throw new EOFException("readShortLittle() - EOF in middle of word at offset " - + numBytesRead); - result |= data << 8; - return result; - } - - public int readUShortLittle() throws IOException { - return (readShortLittle()) & 0x0000FFFF; - } - - /** Read 8 bit signed byte. */ - public byte readByte() throws IOException { - return (byte) read(); - } - - /** Read 32 bit signed int assuming IFF order. */ - public int readChunkSize() throws IOException { - if (isRIFF()) { - return readIntLittle(); - } - { - return readIntBig(); - } - } - - /** Convert a 4 character IFF ID to a String */ - public static String IDToString(int ID) { - byte bar[] = new byte[4]; - bar[0] = (byte) (ID >> 24); - bar[1] = (byte) (ID >> 16); - bar[2] = (byte) (ID >> 8); - bar[3] = (byte) ID; - return new String(bar); - } - - /** - * Parse the stream after reading the first ID and pass the forms and chunks to the ChunkHandler - */ - public void parseAfterHead(ChunkHandler handler) throws IOException { - int numBytes = readChunkSize(); - totalSize = numBytes + 8; - parseChunk(handler, fileId, numBytes); - if (debug) - System.out.println("parse() ------- end"); - } - - /** - * Parse the FORM and pass the chunks to the ChunkHandler The cursor should be positioned right - * after the type field. - */ - void parseForm(ChunkHandler handler, int ID, int numBytes, int type) throws IOException { - if (debug) { - System.out.println("IFF: parseForm >>>>>>>>>>>>>>>>>> BEGIN"); - } - while (numBytes > 8) { - int ckid = readIntBig(); - int size = readChunkSize(); - numBytes -= 8; - if (debug) { - System.out.println("chunk( " + IDToString(ckid) + ", " + size + " )"); - } - if (size < 0) { - throw new IOException("Bad IFF chunk Size: " + IDToString(ckid) + " = 0x" - + Integer.toHexString(ckid) + ", Size = " + size); - } - parseChunk(handler, ckid, size); - if ((size & 1) == 1) - size++; // even-up - numBytes -= size; - if (debug) { - System.out.println("parseForm: numBytes left in form = " + numBytes); - } - } - if (debug) { - System.out.println("IFF: parseForm <<<<<<<<<<<<<<<<<<<< END"); - } - - if (numBytes > 0) { - System.out.println("IFF Parser detected " + numBytes - + " bytes of garbage at end of FORM."); - skip(numBytes); - } - } - - /* - * Parse one chunk from IFF file. After calling handler, make sure stream is positioned at end - * of chunk. - */ - void parseChunk(ChunkHandler handler, int ckid, int numBytes) throws IOException { - long startOffset, endOffset; - int numRead; - startOffset = getOffset(); - if (isForm(ckid)) { - int type = readIntBig(); - if (debug) - System.out.println("parseChunk: form = " + IDToString(ckid) + ", " + numBytes - + ", " + IDToString(type)); - handler.handleForm(this, ckid, numBytes - 4, type); - endOffset = getOffset(); - numRead = (int) (endOffset - startOffset); - if (numRead < numBytes) - parseForm(handler, ckid, (numBytes - numRead), type); - } else { - handler.handleChunk(this, ckid, numBytes); - } - endOffset = getOffset(); - numRead = (int) (endOffset - startOffset); - if (debug) { - System.out.println("parseChunk: endOffset = " + endOffset); - System.out.println("parseChunk: numRead = " + numRead); - } - if ((numBytes & 1) == 1) - numBytes++; // even-up - if (numRead < numBytes) - skip(numBytes - numRead); - } - - public void readHead() throws IOException { - if (debug) - System.out.println("parse() ------- begin"); - numBytesRead = 0; - fileId = readIntBig(); - } - - public boolean isRIFF() { - return (fileId == RIFF_ID); - } - - public boolean isIFF() { - return (fileId == FORM_ID); - } - - /** - * Does the following chunk ID correspond to a container type like FORM? - */ - public boolean isForm(int ckid) { - if (isRIFF()) { - switch (ckid) { - case LIST_ID: - case RIFF_ID: - return true; - default: - return false; - } - } else { - switch (ckid) { - case LIST_ID: - case FORM_ID: - return true; - default: - return false; - } - } - } - -} diff --git a/src/com/jsyn/util/soundfile/WAVEFileParser.java b/src/com/jsyn/util/soundfile/WAVEFileParser.java deleted file mode 100644 index ec9350c..0000000 --- a/src/com/jsyn/util/soundfile/WAVEFileParser.java +++ /dev/null @@ -1,334 +0,0 @@ -/* - * 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.util.soundfile; - -import java.io.EOFException; -import java.io.IOException; - -import com.jsyn.data.FloatSample; -import com.jsyn.data.SampleMarker; -import com.jsyn.util.SampleLoader; - -class WAVEFileParser extends AudioFileParser implements ChunkHandler { - static final short WAVE_FORMAT_PCM = 1; - static final short WAVE_FORMAT_IEEE_FLOAT = 3; - static final short WAVE_FORMAT_EXTENSIBLE = (short) 0xFFFE; - - static final byte[] KSDATAFORMAT_SUBTYPE_IEEE_FLOAT = { - 3, 0, 0, 0, 0, 0, 16, 0, -128, 0, 0, -86, 0, 56, -101, 113 - }; - static final byte[] KSDATAFORMAT_SUBTYPE_PCM = { - 1, 0, 0, 0, 0, 0, 16, 0, -128, 0, 0, -86, 0, 56, -101, 113 - }; - - static final int WAVE_ID = ('W' << 24) | ('A' << 16) | ('V' << 8) | 'E'; - static final int FMT_ID = ('f' << 24) | ('m' << 16) | ('t' << 8) | ' '; - static final int DATA_ID = ('d' << 24) | ('a' << 16) | ('t' << 8) | 'a'; - static final int CUE_ID = ('c' << 24) | ('u' << 16) | ('e' << 8) | ' '; - static final int FACT_ID = ('f' << 24) | ('a' << 16) | ('c' << 8) | 't'; - static final int SMPL_ID = ('s' << 24) | ('m' << 16) | ('p' << 8) | 'l'; - static final int LTXT_ID = ('l' << 24) | ('t' << 16) | ('x' << 8) | 't'; - static final int LABL_ID = ('l' << 24) | ('a' << 16) | ('b' << 8) | 'l'; - - int samplesPerBlock = 0; - int blockAlign = 0; - private int numFactSamples = 0; - private short format; - - WAVEFileParser() { - } - - @Override - FloatSample finish() throws IOException { - if ((byteData == null)) { - throw new IOException("No data found in audio sample."); - } - float[] floatData = new float[numFrames * samplesPerFrame]; - if (bitsPerSample == 16) { - SampleLoader.decodeLittleI16ToF32(byteData, 0, byteData.length, floatData, 0); - } else if (bitsPerSample == 24) { - SampleLoader.decodeLittleI24ToF32(byteData, 0, byteData.length, floatData, 0); - } else if (bitsPerSample == 32) { - if (format == WAVE_FORMAT_IEEE_FLOAT) { - SampleLoader.decodeLittleF32ToF32(byteData, 0, byteData.length, floatData, 0); - } else if (format == WAVE_FORMAT_PCM) { - SampleLoader.decodeLittleI32ToF32(byteData, 0, byteData.length, floatData, 0); - } else { - throw new IOException("WAV: Unsupported format = " + format); - } - } else { - throw new IOException("WAV: Unsupported bitsPerSample = " + bitsPerSample); - } - - return makeSample(floatData); - } - - // typedef struct { - // long dwIdentifier; - // long dwPosition; - // ID fccChunk; - // long dwChunkStart; - // long dwBlockStart; - // long dwSampleOffset; - // } CuePoint; - - /* Parse various chunks encountered in WAV file. */ - void parseCueChunk(IFFParser parser, int ckSize) throws IOException { - int numCuePoints = parser.readIntLittle(); - if (IFFParser.debug) { - System.out.println("WAV: numCuePoints = " + numCuePoints); - } - if ((ckSize - 4) != (6 * 4 * numCuePoints)) - throw new EOFException("Cue chunk too short!"); - for (int i = 0; i < numCuePoints; i++) { - int dwName = parser.readIntLittle(); /* dwName */ - int position = parser.readIntLittle(); // dwPosition - parser.skip(3 * 4); // fccChunk, dwChunkStart, dwBlockStart - int sampleOffset = parser.readIntLittle(); // dwPosition - - if (IFFParser.debug) { - System.out.println("WAV: parseCueChunk: #" + i + ", dwPosition = " + position - + ", dwName = " + dwName + ", dwSampleOffset = " + sampleOffset); - } - SampleMarker cuePoint = findOrCreateCuePoint(dwName); - cuePoint.position = position; - } - } - - void parseLablChunk(IFFParser parser, int ckSize) throws IOException { - int dwName = parser.readIntLittle(); - int textLength = (ckSize - 4) - 1; // don't read NUL terminator - String text = parseString(parser, textLength); - if (IFFParser.debug) { - System.out.println("WAV: label id = " + dwName + ", text = " + text); - } - SampleMarker cuePoint = findOrCreateCuePoint(dwName); - cuePoint.name = text; - } - - void parseLtxtChunk(IFFParser parser, int ckSize) throws IOException { - int dwName = parser.readIntLittle(); - int dwSampleLength = parser.readIntLittle(); - parser.skip(4 + (4 * 2)); // purpose through codepage - int textLength = (ckSize - ((4 * 4) + (4 * 2))) - 1; // don't read NUL - // terminator - if (textLength > 0) { - String text = parseString(parser, textLength); - if (IFFParser.debug) { - System.out.println("WAV: ltxt id = " + dwName + ", dwSampleLength = " - + dwSampleLength + ", text = " + text); - } - SampleMarker cuePoint = findOrCreateCuePoint(dwName); - cuePoint.comment = text; - } - } - - void parseFmtChunk(IFFParser parser, int ckSize) throws IOException { - format = parser.readShortLittle(); - samplesPerFrame = parser.readShortLittle(); - frameRate = parser.readIntLittle(); - parser.readIntLittle(); /* skip dwAvgBytesPerSec */ - blockAlign = parser.readShortLittle(); - bitsPerSample = parser.readShortLittle(); - - if (IFFParser.debug) { - System.out.println("WAV: format = 0x" + Integer.toHexString(format)); - System.out.println("WAV: bitsPerSample = " + bitsPerSample); - System.out.println("WAV: samplesPerFrame = " + samplesPerFrame); - } - bytesPerFrame = blockAlign; - bytesPerSample = bytesPerFrame / samplesPerFrame; - samplesPerBlock = (8 * blockAlign) / bitsPerSample; - - if (format == WAVE_FORMAT_EXTENSIBLE) { - int extraSize = parser.readShortLittle(); - short validBitsPerSample = parser.readShortLittle(); - int channelMask = parser.readIntLittle(); - byte[] guid = new byte[16]; - parser.read(guid); - if (IFFParser.debug) { - System.out.println("WAV: extraSize = " + extraSize); - System.out.println("WAV: validBitsPerSample = " + validBitsPerSample); - System.out.println("WAV: channelMask = " + channelMask); - System.out.print("guid = {"); - for (int i = 0; i < guid.length; i++) { - System.out.print(guid[i] + ", "); - } - System.out.println("}"); - } - if (matchBytes(guid, KSDATAFORMAT_SUBTYPE_IEEE_FLOAT)) { - format = WAVE_FORMAT_IEEE_FLOAT; - } else if (matchBytes(guid, KSDATAFORMAT_SUBTYPE_PCM)) { - format = WAVE_FORMAT_PCM; - } - } - if ((format != WAVE_FORMAT_PCM) && (format != WAVE_FORMAT_IEEE_FLOAT)) { - throw new IOException( - "Only WAVE_FORMAT_PCM and WAVE_FORMAT_IEEE_FLOAT supported. format = " + format); - } - if ((bitsPerSample != 16) && (bitsPerSample != 24) && (bitsPerSample != 32)) { - throw new IOException( - "Only 16 and 24 bit PCM or 32-bit float WAV files supported. width = " - + bitsPerSample); - } - } - - private boolean matchBytes(byte[] bar1, byte[] bar2) { - if (bar1.length != bar2.length) - return false; - for (int i = 0; i < bar1.length; i++) { - if (bar1[i] != bar2[i]) - return false; - } - return true; - } - - private int convertByteToFrame(int byteOffset) throws IOException { - if (blockAlign == 0) { - throw new IOException("WAV file has bytesPerBlock = zero"); - } - if (samplesPerFrame == 0) { - throw new IOException("WAV file has samplesPerFrame = zero"); - } - int nFrames = (samplesPerBlock * byteOffset) / (samplesPerFrame * blockAlign); - return nFrames; - } - - private int calculateNumFrames(int numBytes) throws IOException { - int nFrames; - if (numFactSamples > 0) { - // nFrames = numFactSamples / samplesPerFrame; - nFrames = numFactSamples; // FIXME which is right - } else { - nFrames = convertByteToFrame(numBytes); - } - return nFrames; - } - - // Read fraction in range of 0 to 0xFFFFFFFF and - // convert to 0.0 to 1.0 range. - private double readFraction(IFFParser parser) throws IOException { - // Put L at end or we get -1. - long maxFraction = 0x0FFFFFFFFL; - // Get unsigned fraction. Have to fit in long. - long fraction = (parser.readIntLittle()) & maxFraction; - return (double) fraction / (double) maxFraction; - } - - void parseSmplChunk(IFFParser parser, int ckSize) throws IOException { - parser.readIntLittle(); // Manufacturer - parser.readIntLittle(); // Product - parser.readIntLittle(); // Sample Period - int unityNote = parser.readIntLittle(); - double pitchFraction = readFraction(parser); - originalPitch = unityNote + pitchFraction; - - parser.readIntLittle(); // SMPTE Format - parser.readIntLittle(); // SMPTE Offset - int numLoops = parser.readIntLittle(); - parser.readIntLittle(); // Sampler Data - - int lastCueID = Integer.MAX_VALUE; - for (int i = 0; i < numLoops; i++) { - int cueID = parser.readIntLittle(); - parser.readIntLittle(); // type - int loopStartPosition = parser.readIntLittle(); - // Point to sample one after. - int loopEndPosition = parser.readIntLittle() + 1; - // TODO handle fractional loop sizes? - double endFraction = readFraction(parser); - parser.readIntLittle(); // playCount - - // Use lowest numbered cue. - if (cueID < lastCueID) { - sustainBegin = loopStartPosition; - sustainEnd = loopEndPosition; - } - } - } - - void parseFactChunk(IFFParser parser, int ckSize) throws IOException { - numFactSamples = parser.readIntLittle(); - } - - void parseDataChunk(IFFParser parser, int ckSize) throws IOException { - long numRead; - dataPosition = parser.getOffset(); - if (ifLoadData) { - byteData = new byte[ckSize]; - numRead = parser.read(byteData); - } else { - numRead = parser.skip(ckSize); - } - if (numRead != ckSize) { - throw new EOFException("WAV data chunk too short! Read " + numRead + " instead of " - + ckSize); - } - numFrames = calculateNumFrames(ckSize); - } - - @Override - public void handleForm(IFFParser parser, int ckID, int ckSize, int type) throws IOException { - if ((ckID == IFFParser.RIFF_ID) && (type != WAVE_ID)) - throw new IOException("Bad WAV form type = " + IFFParser.IDToString(type)); - } - - /** - * Called by parse() method to handle chunks in a WAV specific manner. - * - * @param ckID four byte chunk ID such as 'data' - * @param ckSize size of chunk in bytes - * @return number of bytes left in chunk - */ - @Override - public void handleChunk(IFFParser parser, int ckID, int ckSize) throws IOException { - switch (ckID) { - case FMT_ID: - parseFmtChunk(parser, ckSize); - break; - case DATA_ID: - parseDataChunk(parser, ckSize); - break; - case CUE_ID: - parseCueChunk(parser, ckSize); - break; - case FACT_ID: - parseFactChunk(parser, ckSize); - break; - case SMPL_ID: - parseSmplChunk(parser, ckSize); - break; - case LABL_ID: - parseLablChunk(parser, ckSize); - break; - case LTXT_ID: - parseLtxtChunk(parser, ckSize); - break; - default: - break; - } - } - - /* - * (non-Javadoc) - * @see com.softsynth.javasonics.util.AudioSampleLoader#isLittleEndian() - */ - boolean isLittleEndian() { - return true; - } - -} diff --git a/src/com/softsynth/math/AudioMath.java b/src/com/softsynth/math/AudioMath.java deleted file mode 100644 index 6d5ab07..0000000 --- a/src/com/softsynth/math/AudioMath.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright 1998 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.softsynth.math; - -/** - * Miscellaneous math functions useful in Audio - * - * @author (C) 1998 Phil Burk - */ -public class AudioMath { - // use scalar to convert natural log to log_base_10 - private final static double a2dScalar = 20.0 / Math.log(10.0); - public static final int CONCERT_A_PITCH = 69; - public static final double CONCERT_A_FREQUENCY = 440.0; - private static double mConcertAFrequency = CONCERT_A_FREQUENCY; - - /** - * Convert amplitude to decibels. 1.0 is zero dB. 0.5 is -6.02 dB. - */ - public static double amplitudeToDecibels(double amplitude) { - double db = Math.log(amplitude) * a2dScalar; - return db; - } - - /** - * Convert decibels to amplitude. Zero dB is 1.0 and -6.02 dB is 0.5. - */ - public static double decibelsToAmplitude(double decibels) { - double amp = Math.pow(10.0, decibels / 20.0); - return amp; - } - - /** - * Calculate MIDI pitch based on frequency in Hertz. Middle C is 60.0. - */ - public static double frequencyToPitch(double frequency) { - return CONCERT_A_PITCH + 12 * Math.log(frequency / mConcertAFrequency) / Math.log(2.0); - } - - /** - * Calculate frequency in Hertz based on MIDI pitch. Middle C is 60.0. You can use fractional - * pitches so 60.5 would give you a pitch half way between C and C#. - */ - public static double pitchToFrequency(double pitch) { - return mConcertAFrequency * Math.pow(2.0, ((pitch - CONCERT_A_PITCH) * (1.0 / 12.0))); - } - - /** - * This can be used to globally adjust the tuning in JSyn from Concert A at 440.0 Hz to - * a slightly different frequency. Some orchestras use a higher frequency, eg. 441.0. - * This value will be used by pitchToFrequency() and frequencyToPitch(). - * - * @param concertAFrequency - */ - public static void setConcertAFrequency(double concertAFrequency) { - mConcertAFrequency = concertAFrequency; - } - - public static double getConcertAFrequency() { - return mConcertAFrequency; - } - - /** Convert a delta value in semitones to a frequency multiplier. - * @param semitones - * @return scaler For example 2.0 for an input of 12.0 semitones. - */ - public static double semitonesToFrequencyScaler(double semitones) { - return Math.pow(2.0, semitones / 12.0); - } -} diff --git a/src/com/softsynth/math/ChebyshevPolynomial.java b/src/com/softsynth/math/ChebyshevPolynomial.java deleted file mode 100644 index bc0e854..0000000 --- a/src/com/softsynth/math/ChebyshevPolynomial.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 1997 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.softsynth.math; - -/** - * ChebyshevPolynomial
- * Used to generate data for waveshaping table oscillators. - * - * @author Nick Didkovsky (C) 1997 Phil Burk and Nick Didkovsky - */ - -public class ChebyshevPolynomial { - static final Polynomial twoX = new Polynomial(2, 0); - static final Polynomial one = new Polynomial(1); - static final Polynomial oneX = new Polynomial(1, 0); - - /** - * Calculates Chebyshev polynomial of specified integer order. Recursively generated using - * relation Tk+1(x) = 2xTk(x) - Tk-1(x) - * - * @return Chebyshev polynomial of specified order - */ - public static Polynomial T(int order) { - if (order == 0) - return one; - else if (order == 1) - return oneX; - else - return Polynomial.minus(Polynomial.mult(T(order - 1), (twoX)), T(order - 2)); - } -} diff --git a/src/com/softsynth/math/FourierMath.java b/src/com/softsynth/math/FourierMath.java deleted file mode 100644 index d133d7f..0000000 --- a/src/com/softsynth/math/FourierMath.java +++ /dev/null @@ -1,254 +0,0 @@ -/* - * 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.softsynth.math; - -//Simple Fast Fourier Transform. -public class FourierMath { - static private final int MAX_SIZE_LOG_2 = 16; - static BitReverseTable[] reverseTables = new BitReverseTable[MAX_SIZE_LOG_2]; - static DoubleSineTable[] sineTables = new DoubleSineTable[MAX_SIZE_LOG_2]; - static FloatSineTable[] floatSineTables = new FloatSineTable[MAX_SIZE_LOG_2]; - - private static class DoubleSineTable { - double[] sineValues; - - DoubleSineTable(int numBits) { - int len = 1 << numBits; - sineValues = new double[1 << numBits]; - for (int i = 0; i < len; i++) { - sineValues[i] = Math.sin((i * Math.PI * 2.0) / len); - } - } - } - - private static double[] getDoubleSineTable(int n) { - DoubleSineTable sineTable = sineTables[n]; - if (sineTable == null) { - sineTable = new DoubleSineTable(n); - sineTables[n] = sineTable; - } - return sineTable.sineValues; - } - - private static class FloatSineTable { - float[] sineValues; - - FloatSineTable(int numBits) { - int len = 1 << numBits; - sineValues = new float[1 << numBits]; - for (int i = 0; i < len; i++) { - sineValues[i] = (float) Math.sin((i * Math.PI * 2.0) / len); - } - } - } - - private static float[] getFloatSineTable(int n) { - FloatSineTable sineTable = floatSineTables[n]; - if (sineTable == null) { - sineTable = new FloatSineTable(n); - floatSineTables[n] = sineTable; - } - return sineTable.sineValues; - } - - private static class BitReverseTable { - int[] reversedBits; - - BitReverseTable(int numBits) { - reversedBits = new int[1 << numBits]; - for (int i = 0; i < reversedBits.length; i++) { - reversedBits[i] = reverseBits(i, numBits); - } - } - - static int reverseBits(int index, int numBits) { - int i, rev; - - for (i = rev = 0; i < numBits; i++) { - rev = (rev << 1) | (index & 1); - index >>= 1; - } - - return rev; - } - } - - private static int[] getReverseTable(int n) { - BitReverseTable reverseTable = reverseTables[n]; - if (reverseTable == null) { - reverseTable = new BitReverseTable(n); - reverseTables[n] = reverseTable; - } - return reverseTable.reversedBits; - } - - /** - * Calculate the amplitude of the sine wave associated with each bin of a complex FFT result. - * - * @param ar - * @param ai - * @param magnitudes - */ - public static void calculateMagnitudes(double ar[], double ai[], double[] magnitudes) { - for (int i = 0; i < magnitudes.length; ++i) { - magnitudes[i] = Math.sqrt((ar[i] * ar[i]) + (ai[i] * ai[i])); - } - } - - /** - * Calculate the amplitude of the sine wave associated with each bin of a complex FFT result. - * - * @param ar - * @param ai - * @param magnitudes - */ - public static void calculateMagnitudes(float ar[], float ai[], float[] magnitudes) { - for (int i = 0; i < magnitudes.length; ++i) { - magnitudes[i] = (float) Math.sqrt((ar[i] * ar[i]) + (ai[i] * ai[i])); - } - } - - public static void transform(int sign, int n, double ar[], double ai[]) { - double scale = (sign > 0) ? (2.0 / n) : (0.5); - - int numBits = FourierMath.numBits(n); - int[] reverseTable = getReverseTable(numBits); - double[] sineTable = getDoubleSineTable(numBits); - int mask = n - 1; - int cosineOffset = n / 4; // phase offset between cos and sin - - int i, j; - for (i = 0; i < n; i++) { - j = reverseTable[i]; - if (j >= i) { - double tempr = ar[j] * scale; - double tempi = ai[j] * scale; - ar[j] = ar[i] * scale; - ai[j] = ai[i] * scale; - ar[i] = tempr; - ai[i] = tempi; - } - } - - int mmax, stride; - int numerator = sign * n; - for (mmax = 1, stride = 2 * mmax; mmax < n; mmax = stride, stride = 2 * mmax) { - int phase = 0; - int phaseIncrement = numerator / (2 * mmax); - for (int m = 0; m < mmax; ++m) { - double wr = sineTable[(phase + cosineOffset) & mask]; // cosine - double wi = sineTable[phase]; - - for (i = m; i < n; i += stride) { - j = i + mmax; - double tr = (wr * ar[j]) - (wi * ai[j]); - double ti = (wr * ai[j]) + (wi * ar[j]); - ar[j] = ar[i] - tr; - ai[j] = ai[i] - ti; - ar[i] += tr; - ai[i] += ti; - } - - phase = (phase + phaseIncrement) & mask; - } - mmax = stride; - } - } - - public static void transform(int sign, int n, float ar[], float ai[]) { - float scale = (sign > 0) ? (2.0f / n) : (0.5f); - - int numBits = FourierMath.numBits(n); - int[] reverseTable = getReverseTable(numBits); - float[] sineTable = getFloatSineTable(numBits); - int mask = n - 1; - int cosineOffset = n / 4; // phase offset between cos and sin - - int i, j; - for (i = 0; i < n; i++) { - j = reverseTable[i]; - if (j >= i) { - float tempr = ar[j] * scale; - float tempi = ai[j] * scale; - ar[j] = ar[i] * scale; - ai[j] = ai[i] * scale; - ar[i] = tempr; - ai[i] = tempi; - } - } - - int mmax, stride; - int numerator = sign * n; - for (mmax = 1, stride = 2 * mmax; mmax < n; mmax = stride, stride = 2 * mmax) { - int phase = 0; - int phaseIncrement = numerator / (2 * mmax); - for (int m = 0; m < mmax; ++m) { - float wr = sineTable[(phase + cosineOffset) & mask]; // cosine - float wi = sineTable[phase]; - - for (i = m; i < n; i += stride) { - j = i + mmax; - float tr = (wr * ar[j]) - (wi * ai[j]); - float ti = (wr * ai[j]) + (wi * ar[j]); - ar[j] = ar[i] - tr; - ai[j] = ai[i] - ti; - ar[i] += tr; - ai[i] += ti; - } - - phase = (phase + phaseIncrement) & mask; - } - mmax = stride; - } - } - - /** - * Calculate log2(n) - * - * @param powerOf2 must be a power of two, for example 512 or 1024 - * @return for example, 9 for an input value of 512 - */ - public static int numBits(int powerOf2) { - int i; - assert ((powerOf2 & (powerOf2 - 1)) == 0); // is it a power of 2? - for (i = -1; powerOf2 > 0; powerOf2 = powerOf2 >> 1, i++) - ; - return i; - } - - /** - * Calculate an FFT in place, modifying the input arrays. - * - * @param n - * @param ar - * @param ai - */ - public static void fft(int n, double ar[], double ai[]) { - transform(1, n, ar, ai); // TODO -1 or 1 - } - - /** - * Calculate an inverse FFT in place, modifying the input arrays. - * - * @param n - * @param ar - * @param ai - */ - public static void ifft(int n, double ar[], double ai[]) { - transform(-1, n, ar, ai); // TODO -1 or 1 - } -} diff --git a/src/com/softsynth/math/JustRatio.java b/src/com/softsynth/math/JustRatio.java deleted file mode 100644 index f4070b4..0000000 --- a/src/com/softsynth/math/JustRatio.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * 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.softsynth.math; - -public class JustRatio { - public long numerator; - public long denominator; - - public JustRatio(long numerator, long denominator) { - this.numerator = numerator; - this.denominator = denominator; - } - - public JustRatio(int numerator, int denominator) { - this.numerator = numerator; - this.denominator = denominator; - } - - public double getValue() { - return (double) numerator / denominator; - } - - public void invert() { - long temp = denominator; - denominator = numerator; - numerator = temp; - } - - @Override - public String toString() { - return numerator + "/" + denominator; - } -} diff --git a/src/com/softsynth/math/Polynomial.java b/src/com/softsynth/math/Polynomial.java deleted file mode 100644 index 5f29f38..0000000 --- a/src/com/softsynth/math/Polynomial.java +++ /dev/null @@ -1,253 +0,0 @@ -/* - * Copyright 1997 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.softsynth.math; - -import java.util.Vector; - -/** - * Polynomial
- * Implement polynomial using Vector as coefficient holder. Element index is power of X, value at a - * given index is coefficient.
- *
- * - * @author Nick Didkovsky, (C) 1997 Phil Burk and Nick Didkovsky - */ - -public class Polynomial { - - private final Vector terms; - - class DoubleHolder { - double value; - - public DoubleHolder(double val) { - value = val; - } - - public double get() { - return value; - } - - public void set(double val) { - value = val; - } - } - - /** create a polynomial with no terms */ - public Polynomial() { - terms = new Vector(); - } - - /** create a polynomial with one term of specified constant */ - public Polynomial(double c0) { - this(); - appendTerm(c0); - } - - /** create a polynomial with two terms with specified coefficients */ - public Polynomial(double c1, double c0) { - this(c0); - appendTerm(c1); - } - - /** create a polynomial with specified coefficients */ - public Polynomial(double c2, double c1, double c0) { - this(c1, c0); - appendTerm(c2); - } - - /** create a polynomial with specified coefficients */ - public Polynomial(double c3, double c2, double c1, double c0) { - this(c2, c1, c0); - appendTerm(c3); - } - - /** create a polynomial with specified coefficients */ - public Polynomial(double c4, double c3, double c2, double c1, double c0) { - this(c3, c2, c1, c0); - appendTerm(c4); - } - - /** - * Append a term with specified coefficient. Power will be next available order (ie if the - * polynomial is of order 2, appendTerm will supply the coefficient for x^3 - */ - public void appendTerm(double coefficient) { - terms.addElement(new DoubleHolder(coefficient)); - } - - /** Set the coefficient of given term */ - public void setTerm(double coefficient, int power) { - // If setting a term greater than the current order of the polynomial, pad with zero terms - int size = terms.size(); - if (power >= size) { - for (int i = 0; i < (power - size + 1); i++) { - appendTerm(0); - } - } - ((DoubleHolder) terms.elementAt(power)).set(coefficient); - } - - /** - * Add the coefficient of given term to the specified coefficient. ex. addTerm(3, 1) add 3x to a - * polynomial, addTerm(4, 3) adds 4x^3 - */ - public void addTerm(double coefficient, int power) { - setTerm(coefficient + get(power), power); - } - - /** @return coefficient of nth term (first term=0) */ - public double get(int power) { - if (power >= terms.size()) - return 0.0; - else - return ((DoubleHolder) terms.elementAt(power)).get(); - } - - /** @return number of terms in this polynomial */ - public int size() { - return terms.size(); - } - - /** - * Add two polynomials together - * - * @return new Polynomial that is the sum of p1 and p2 - */ - public static Polynomial plus(Polynomial p1, Polynomial p2) { - Polynomial sum = new Polynomial(); - for (int i = 0; i < Math.max(p1.size(), p2.size()); i++) { - sum.appendTerm(p1.get(i) + p2.get(i)); - } - return sum; - } - - /** - * Subtract polynomial from another. (First arg - Second arg) - * - * @return new Polynomial p1 - p2 - */ - public static Polynomial minus(Polynomial p1, Polynomial p2) { - Polynomial sum = new Polynomial(); - for (int i = 0; i < Math.max(p1.size(), p2.size()); i++) { - sum.appendTerm(p1.get(i) - p2.get(i)); - } - return sum; - } - - /** - * Multiply two Polynomials - * - * @return new Polynomial that is the product p1 * p2 - */ - - public static Polynomial mult(Polynomial p1, Polynomial p2) { - Polynomial product = new Polynomial(); - for (int i = 0; i < p1.size(); i++) { - for (int j = 0; j < p2.size(); j++) { - product.addTerm(p1.get(i) * p2.get(j), i + j); - } - } - return product; - } - - /** - * Multiply a Polynomial by a scaler - * - * @return new Polynomial that is the product p1 * p2 - */ - - public static Polynomial mult(double scaler, Polynomial p1) { - Polynomial product = new Polynomial(); - for (int i = 0; i < p1.size(); i++) { - product.appendTerm(p1.get(i) * scaler); - } - return product; - } - - /** Evaluate this polynomial for x */ - public double evaluate(double x) { - double result = 0.0; - for (int i = 0; i < terms.size(); i++) { - result += get(i) * Math.pow(x, i); - } - return result; - } - - @Override - public String toString() { - String s = ""; - if (size() == 0) - s = "empty polynomial"; - boolean somethingPrinted = false; - for (int i = size() - 1; i >= 0; i--) { - if (get(i) != 0.0) { - if (somethingPrinted) - s += " + "; - String coeff = ""; - // if (get(i) == (int)(get(i))) - // coeff = (int)(get(i)) + ""; - if ((get(i) != 1.0) || (i == 0)) - coeff += get(i); - if (i == 0) - s += coeff; - else { - String power = ""; - if (i != 1) - power = "^" + i; - s += coeff + "x" + power; - } - somethingPrinted = true; - } - } - return s; - } - - public static void main(String args[]) { - Polynomial p1 = new Polynomial(); - System.out.println("p1=" + p1); - Polynomial p2 = new Polynomial(3); - System.out.println("p2=" + p2); - Polynomial p3 = new Polynomial(2, 3); - System.out.println("p3=" + p3); - Polynomial p4 = new Polynomial(1, 2, 3); - System.out.println("p4=" + p4); - System.out.println("p4*5=" + Polynomial.mult(5.0, p4)); - - System.out.println(p4.evaluate(10)); - - System.out.println(Polynomial.plus(p4, p1)); - System.out.println(Polynomial.minus(p4, p3)); - p4.setTerm(12.2, 5); - System.out.println(p4); - p4.addTerm(0.8, 5); - System.out.println(p4); - p4.addTerm(0.8, 7); - System.out.println(p4); - System.out.println(Polynomial.mult(p3, p2)); - System.out.println(Polynomial.mult(p3, p3)); - System.out.println(Polynomial.mult(p2, p2)); - - Polynomial t2 = new Polynomial(2, 0, -1); // 2x^2-1, Chebyshev Polynomial of order 2 - Polynomial t3 = new Polynomial(4, 0, -3, 0); // 4x^3-3x, Chebyshev Polynomial of order 3 - // Calculate Chebyshev Polynomial of order 4 from relation Tk+1(x) = 2xTk(x) - Tk-1(x) - Polynomial t4 = Polynomial.minus(Polynomial.mult(t3, (new Polynomial(2, 0))), t2); - System.out.println(t2 + "\n" + t3 + "\n" + t4); - // com.softsynth.jmsl.util - - } -} diff --git a/src/com/softsynth/math/PolynomialTableData.java b/src/com/softsynth/math/PolynomialTableData.java deleted file mode 100644 index 676c77c..0000000 --- a/src/com/softsynth/math/PolynomialTableData.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright 1997 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.softsynth.math; - -/** - * PolynomialTableData
- * Provides an array of double[] containing data generated by a polynomial.
- * This is typically used with ChebyshevPolynomial. Input to Polynomial is -1..1, output is -1..1. - * - * @author Nick Didkovsky (C) 1997 Phil Burk and Nick Didkovsky - * @see ChebyshevPolynomial - * @see Polynomial - */ - -public class PolynomialTableData { - - double[] data; - Polynomial polynomial; - - /** - * Constructor which fills double[numFrames] with Polynomial data -1..1
- * Note that any Polynomial can plug in here, just make sure output is -1..1 when input ranges - * from -1..1 - */ - public PolynomialTableData(Polynomial polynomial, int numFrames) { - data = new double[numFrames]; - this.polynomial = polynomial; - buildData(); - } - - public double[] getData() { - return data; - } - - void buildData() { - double xInterval = 2.0 / (data.length - 1); // FIXED, added "- 1" - double x; - for (int i = 0; i < data.length; i++) { - x = i * xInterval - 1.0; - data[i] = polynomial.evaluate(x); - // System.out.println("x = " + x + ", p(x) = " + data[i] ); - } - - } - - public static void main(String args[]) { - PolynomialTableData chebData = new PolynomialTableData(ChebyshevPolynomial.T(2), 8); - } - -} diff --git a/src/com/softsynth/math/PrimeFactors.java b/src/com/softsynth/math/PrimeFactors.java deleted file mode 100644 index 06c0d55..0000000 --- a/src/com/softsynth/math/PrimeFactors.java +++ /dev/null @@ -1,244 +0,0 @@ -/* - * Copyright 2011 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.softsynth.math; - -import java.util.ArrayList; - -/** - * Tool for factoring primes and prime ratios. This class contains a static array of primes - * generated using the Sieve of Eratosthenes. - * - * @author Phil Burk (C) 2011 Mobileer Inc - */ -public class PrimeFactors { - private static final int SIEVE_SIZE = 1000; - private static int[] primes; - private final int[] factors; - - static { - // Use Sieve of Eratosthenes to fill Prime table - boolean[] sieve = new boolean[SIEVE_SIZE]; - ArrayList primeList = new ArrayList(); - int i = 2; - while (i < (SIEVE_SIZE / 2)) { - if (!sieve[i]) { - primeList.add(i); - int multiple = 2 * i; - while (multiple < SIEVE_SIZE) { - sieve[multiple] = true; - multiple += i; - } - } - i += 1; - } - primes = primeListToArray(primeList); - } - - private static int[] primeListToArray(ArrayList primeList) { - int[] primes = new int[primeList.size()]; - for (int i = 0; i < primes.length; i++) { - primes[i] = primeList.get(i); - } - return primes; - } - - public PrimeFactors(int[] factors) { - this.factors = factors; - } - - public PrimeFactors(int numerator, int denominator) { - int[] topFactors = factor(numerator); - int[] bottomFactors = factor(denominator); - factors = subtract(topFactors, bottomFactors); - } - - public PrimeFactors subtract(PrimeFactors pf) { - return new PrimeFactors(subtract(factors, pf.factors)); - } - - public PrimeFactors add(PrimeFactors pf) { - return new PrimeFactors(add(factors, pf.factors)); - } - - public static int[] subtract(int[] factorsA, int[] factorsB) { - int max; - int min; - if (factorsA.length > factorsB.length) { - max = factorsA.length; - min = factorsB.length; - } else { - - min = factorsA.length; - max = factorsB.length; - } - ArrayList primeList = new ArrayList(); - int i; - for (i = 0; i < min; i++) { - primeList.add(factorsA[i] - factorsB[i]); - } - if (factorsA.length > factorsB.length) { - for (; i < max; i++) { - primeList.add(factorsA[i]); - } - } else { - for (; i < max; i++) { - primeList.add(0 - factorsB[i]); - } - } - trimPrimeList(primeList); - return primeListToArray(primeList); - } - - public static int[] add(int[] factorsA, int[] factorsB) { - int max; - int min; - if (factorsA.length > factorsB.length) { - max = factorsA.length; - min = factorsB.length; - } else { - min = factorsA.length; - max = factorsB.length; - } - ArrayList primeList = new ArrayList(); - int i; - for (i = 0; i < min; i++) { - primeList.add(factorsA[i] + factorsB[i]); - } - if (factorsA.length > factorsB.length) { - for (; i < max; i++) { - primeList.add(factorsA[i]); - } - } else if (factorsB.length > factorsA.length) { - for (; i < max; i++) { - primeList.add(factorsB[i]); - } - } - trimPrimeList(primeList); - return primeListToArray(primeList); - } - - private static void trimPrimeList(ArrayList primeList) { - int i; - // trim zero factors off end. - for (i = primeList.size() - 1; i >= 0; i--) { - if (primeList.get(i) == 0) { - primeList.remove(i); - } else { - break; - } - } - } - - public static int[] factor(int n) { - ArrayList primeList = new ArrayList(); - int i = 0; - int p = primes[i]; - int exponent = 0; - while (n > 1) { - // does the prime number divide evenly into n? - int d = n / p; - int m = d * p; - if (m == n) { - n = d; - exponent += 1; - } else { - primeList.add(exponent); - exponent = 0; - i += 1; - p = primes[i]; - } - } - if (exponent > 0) { - primeList.add(exponent); - } - return primeListToArray(primeList); - } - - /** - * Get prime from table. - * - * - * @param n Warning: Do not exceed getPrimeCount()-1. - * @return Nth prime number, the 0th prime is 2 - */ - public static int getPrime(int n) { - return primes[n]; - } - - /** - * @return the number of primes stored in the table - */ - public static int getPrimeCount() { - return primes.length; - } - - public JustRatio getJustRatio() { - long n = 1; - long d = 1; - for (int i = 0; i < factors.length; i++) { - int exponent = factors[i]; - int p = primes[i]; - if (exponent > 0) { - for (int k = 0; k < exponent; k++) { - n = n * p; - } - } else if (exponent < 0) { - exponent = 0 - exponent; - for (int k = 0; k < exponent; k++) { - d = d * p; - } - } - } - return new JustRatio(n, d); - } - - public int[] getFactors() { - return factors.clone(); - } - - @Override - public String toString() { - StringBuffer buffer = new StringBuffer(); - printFactors(buffer, 1); - buffer.append("/"); - printFactors(buffer, -1); - return buffer.toString(); - } - - private void printFactors(StringBuffer buffer, int sign) { - boolean gotSome = false; - for (int i = 0; i < factors.length; i++) { - int pf = factors[i] * sign; - if (pf > 0) { - if (gotSome) - buffer.append('*'); - int prime = primes[i]; - if (pf == 1) { - buffer.append("" + prime); - } else if (pf == 2) { - buffer.append(prime + "*" + prime); - } else if (pf > 2) { - buffer.append("(" + prime + "^" + pf + ")"); - } - gotSome = true; - } - } - if (!gotSome) { - buffer.append("1"); - } - } -} diff --git a/src/com/softsynth/shared/time/ScheduledCommand.java b/src/com/softsynth/shared/time/ScheduledCommand.java deleted file mode 100644 index 5b600a7..0000000 --- a/src/com/softsynth/shared/time/ScheduledCommand.java +++ /dev/null @@ -1,21 +0,0 @@ -/* - * 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.softsynth.shared.time; - -public interface ScheduledCommand { - public void run(); -} diff --git a/src/com/softsynth/shared/time/ScheduledQueue.java b/src/com/softsynth/shared/time/ScheduledQueue.java deleted file mode 100644 index 367e4f8..0000000 --- a/src/com/softsynth/shared/time/ScheduledQueue.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * 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.softsynth.shared.time; - -import java.util.LinkedList; -import java.util.List; -import java.util.SortedMap; -import java.util.TreeMap; - -/** - * Store objects in time sorted order. - */ -public class ScheduledQueue { - private final SortedMap> timeNodes; - - public ScheduledQueue() { - timeNodes = new TreeMap>(); - } - - public boolean isEmpty() { - return timeNodes.isEmpty(); - } - - public synchronized void add(TimeStamp time, T obj) { - List timeList = timeNodes.get(time); - if (timeList == null) { - timeList = new LinkedList(); - timeNodes.put(time, timeList); - } - timeList.add(obj); - } - - public synchronized List removeNextList(TimeStamp time) { - List timeList = null; - if (!timeNodes.isEmpty()) { - TimeStamp lowestTime = timeNodes.firstKey(); - // Is the lowest time before or equal to the specified time. - if (lowestTime.compareTo(time) <= 0) { - timeList = timeNodes.remove(lowestTime); - } - } - return timeList; - } - - public synchronized Object removeNext(TimeStamp time) { - Object next = null; - if (!timeNodes.isEmpty()) { - TimeStamp lowestTime = timeNodes.firstKey(); - // Is the lowest time before or equal to the specified time. - if (lowestTime.compareTo(time) <= 0) { - List timeList = timeNodes.get(lowestTime); - if (timeList != null) { - next = timeList.remove(0); - if (timeList.isEmpty()) { - timeNodes.remove(lowestTime); - } - } - } - } - return next; - } - - public synchronized void clear() { - timeNodes.clear(); - } - - public TimeStamp getNextTime() { - return timeNodes.firstKey(); - } - -} diff --git a/src/com/softsynth/shared/time/TimeStamp.java b/src/com/softsynth/shared/time/TimeStamp.java deleted file mode 100644 index 6d243ed..0000000 --- a/src/com/softsynth/shared/time/TimeStamp.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * 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.softsynth.shared.time; - -/** - * @author Phil Burk, (C) 2009 Mobileer Inc - */ -public class TimeStamp implements Comparable { - private final double time; - - public TimeStamp(double time) { - this.time = time; - } - - public double getTime() { - return time; - } - - /** - * @return -1 if (this < t2), 0 if equal, or +1 - */ - @Override - public int compareTo(TimeStamp t2) { - if (time < t2.time) - return -1; - else if (time == t2.time) - return 0; - else - return 1; - } - - /** - * Create a new TimeStamp at a relative offset in seconds. - * - * @param delta - * @return earlier or later TimeStamp - */ - public TimeStamp makeRelative(double delta) { - return new TimeStamp(time + delta); - } - -} diff --git a/src/main/java/com/jsyn/JSyn.java b/src/main/java/com/jsyn/JSyn.java new file mode 100644 index 0000000..bbc2891 --- /dev/null +++ b/src/main/java/com/jsyn/JSyn.java @@ -0,0 +1,78 @@ +/* + * Copyright 2010 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; + +import java.sql.Date; +import java.util.GregorianCalendar; + +import com.jsyn.devices.AudioDeviceManager; +import com.jsyn.engine.SynthesisEngine; + +/** + * JSyn Synthesizer for Java. Use this factory class to create a synthesizer. This code demonstrates + * how to start playing a sine wave: + * + *

+	// Create a context for the synthesizer.
+	synth = JSyn.createSynthesizer();
+
+	// Start synthesizer using default stereo output at 44100 Hz.
+	synth.start();
+
+	// Add a tone generator.
+	synth.add( osc = new SineOscillator() );
+	// Add a stereo audio output unit.
+	synth.add( lineOut = new LineOut() );
+
+	// Connect the oscillator to both channels of the output.
+	osc.output.connect( 0, lineOut.input, 0 );
+	osc.output.connect( 0, lineOut.input, 1 );
+
+	// Set the frequency and amplitude for the sine wave.
+	osc.frequency.set( 345.0 );
+	osc.amplitude.set( 0.6 );
+
+	// We only need to start the LineOut. It will pull data from the oscillator.
+	lineOut.start();
+ 
+ * + * @author Phil Burk (C) 2010 Mobileer Inc + */ +public class JSyn { + // Update these for every release. + private final static int VERSION_MAJOR = 16; + private final static int VERSION_MINOR = 8; + private final static int VERSION_REVISION = 1; + public final static int BUILD_NUMBER = 464; + private final static long BUILD_TIME = new GregorianCalendar(2017, + GregorianCalendar.OCTOBER, 16).getTime().getTime(); + + public final static String VERSION = VERSION_MAJOR + "." + VERSION_MINOR + "." + + VERSION_REVISION; + public final static int VERSION_CODE = (VERSION_MAJOR << 16) + (VERSION_MINOR << 8) + + VERSION_REVISION; + public final static String VERSION_TEXT = "V" + VERSION + " (build " + BUILD_NUMBER + ", " + + (new Date(BUILD_TIME)) + ")"; + + public static Synthesizer createSynthesizer() { + return new SynthesisEngine(); + } + + public static Synthesizer createSynthesizer(AudioDeviceManager audioDeviceManager) { + return new SynthesisEngine(audioDeviceManager); + } +} diff --git a/src/main/java/com/jsyn/Synthesizer.java b/src/main/java/com/jsyn/Synthesizer.java new file mode 100644 index 0000000..bfabb4c --- /dev/null +++ b/src/main/java/com/jsyn/Synthesizer.java @@ -0,0 +1,202 @@ +/* + * Copyright 2010 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; + +import com.jsyn.devices.AudioDeviceManager; +import com.jsyn.unitgen.UnitGenerator; +import com.softsynth.shared.time.ScheduledCommand; +import com.softsynth.shared.time.TimeStamp; + +/** + * A synthesizer used by JSyn to generate audio. The synthesizer executes a network of unit + * generators to create an audio signal. + * + * @author Phil Burk (C) 2010 Mobileer Inc + */ +public interface Synthesizer { + + public final static int FRAMES_PER_BLOCK = 8; + + /** + * Starts a background thread that generates audio using the default frame rate of 44100 and two + * stereo output channels. + */ + public void start(); + + /** + * Starts a background thread that generates audio using the specified frame rate and two stereo + * output channels. + * + * @param frameRate in Hertz + */ + public void start(int frameRate); + + /** + * Starts the synthesizer using specific audio devices. + *

+ * Note that using more than 2 channels will probably require the use of JPortAudio because + * JavaSound currently does not support more than two channels. + * JPortAudio is available at + * http://www.softsynth.com/jsyn/developers/download.php. + *

+ * If you use more than 2 inputs or outputs then you will probably want to use {@link com.jsyn.unitgen.ChannelIn} + * or {@link com.jsyn.unitgen.ChannelOut}, which can be associated with any indexed channel. + * + * @param frameRate in Hertz + * @param inputDeviceID obtained from an {@link AudioDeviceManager} or pass + * AudioDeviceManager.USE_DEFAULT_DEVICE + * @param numInputChannels 0 for no input, 1 for mono, 2 for stereo, etcetera + * @param ouputDeviceID obtained from an AudioDeviceManager or pass + * AudioDeviceManager.USE_DEFAULT_DEVICE + * @param numOutputChannels 0 for no output, 1 for mono, 2 for stereo, etcetera + */ + public void start(int frameRate, int inputDeviceID, int numInputChannels, int ouputDeviceID, + int numOutputChannels); + + /** @return JSyn version as a string */ + public String getVersion(); + + /** @return version as an integer that always increases */ + public int getVersionCode(); + + /** Stops the background thread that generates the audio. */ + public void stop(); + + /** + * An AudioDeviceManager is an interface to audio hardware. It might be implemented using + * JavaSound or a wrapper around PortAudio. + * + * @return audio device manager being used by the synthesizer. + */ + public AudioDeviceManager getAudioDeviceManager(); + + /** @return the frame rate in samples per second */ + public int getFrameRate(); + + /** + * Add a unit generator to the synthesizer so it can be played. This is required before starting + * or connecting a unit generator into a network. + * + * @param ugen a unit generator to be executed by the synthesizer + */ + public void add(UnitGenerator ugen); + + /** Removes a unit generator added using add(). */ + public void remove(UnitGenerator ugen); + + /** @return the current audio time in seconds */ + public double getCurrentTime(); + + /** + * Start a unit generator at the specified time. This is not needed if a unit generator's output + * is connected to other units. Typically you only need to start units that have no outputs, for + * example LineOut or ChannelOut. + */ + public void startUnit(UnitGenerator unit, double time); + + public void startUnit(UnitGenerator unit, TimeStamp timeStamp); + + /** + * The startUnit and stopUnit methods are mainly for internal use. + * Please call unit.start() or unit.stop() instead. + * @param unit + */ + public void startUnit(UnitGenerator unit); + + public void stopUnit(UnitGenerator unit, double time); + + public void stopUnit(UnitGenerator unit, TimeStamp timeStamp); + + /** + * The startUnit and stopUnit methods are mainly for internal use. + * Please call unit.start() or unit.stop() instead. + * @param unit + */ + public void stopUnit(UnitGenerator unit); + + /** + * Sleep until the specified audio time is reached. In non-real-time mode, this will enable the + * synthesizer to run. + */ + public void sleepUntil(double time) throws InterruptedException; + + /** + * Sleep for the specified audio time duration. In non-real-time mode, this will enable the + * synthesizer to run. + */ + public void sleepFor(double duration) throws InterruptedException; + + /** + * If set true then the synthesizer will generate audio in real-time. Set it true for live + * audio. If false then JSyn will run in non-real-time mode. This can be used to generate audio + * to be written to a file. The default is true. + * + * @param realTime + */ + public void setRealTime(boolean realTime); + + /** Is JSyn running in real-time mode? */ + public boolean isRealTime(); + + /** Create a TimeStamp using the current audio time. */ + public TimeStamp createTimeStamp(); + + /** @return the current CPU usage as a fraction between 0.0 and 1.0 */ + public double getUsage(); + + /** @return inverse of frameRate, to avoid expensive divides */ + public double getFramePeriod(); + + /** + * This count is not reset if you stop and restart. + * + * @return number of frames synthesized + */ + public long getFrameCount(); + + /** Queue a command to be processed at a specific time in the background audio thread. */ + public void scheduleCommand(TimeStamp timeStamp, ScheduledCommand command); + + /** Queue a command to be processed at a specific time in the background audio thread. */ + public void scheduleCommand(double time, ScheduledCommand command); + + /** Queue a command to be processed as soon as possible in the background audio thread. */ + public void queueCommand(ScheduledCommand command); + + /** + * Clear all scheduled commands from the queue. + * Commands will be discarded. + */ + public void clearCommandQueue(); + + /** + * @return true if the Synthesizer has been started + */ + public boolean isRunning(); + + /** + * Add a task that will be run repeatedly on the Audio Thread before it generates every new block of Audio. + * This task must be very quick and should not perform any blocking operations. If you are not + * certain that you need an Audio rate task then don't use this. + * + * @param task + */ + public void addAudioTask(Runnable task); + + public void removeAudioTask(Runnable task); + +} diff --git a/src/main/java/com/jsyn/apps/AboutJSyn.java b/src/main/java/com/jsyn/apps/AboutJSyn.java new file mode 100644 index 0000000..0624591 --- /dev/null +++ b/src/main/java/com/jsyn/apps/AboutJSyn.java @@ -0,0 +1,114 @@ +/* + * Copyright 1997 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.apps; + +import java.awt.GridLayout; + +import javax.swing.JApplet; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.SwingConstants; + +import com.jsyn.JSyn; +import com.jsyn.Synthesizer; +import com.jsyn.swing.JAppletFrame; +import com.jsyn.swing.PortControllerFactory; +import com.jsyn.unitgen.LineOut; +import com.jsyn.unitgen.LinearRamp; +import com.jsyn.unitgen.SineOscillator; +import com.jsyn.unitgen.UnitOscillator; + +/** + * Show the version of JSyn and play some sine waves. This program will be run if you double click + * the JSyn jar file. + * + * @author Phil Burk (C) 2011 Mobileer Inc + */ +public class AboutJSyn extends JApplet { + private static final long serialVersionUID = -2704222221111608377L; + private Synthesizer synth; + private UnitOscillator osc1; + private UnitOscillator osc2; + private LinearRamp lag; + private LineOut lineOut; + + @Override + public void init() { + synth = JSyn.createSynthesizer(); + + // Add a tone generator. + synth.add(osc1 = new SineOscillator()); + synth.add(osc2 = new SineOscillator()); + // Add a lag to smooth out amplitude changes and avoid pops. + synth.add(lag = new LinearRamp()); + // Add an output mixer. + synth.add(lineOut = new LineOut()); + // Connect the oscillator to the output. + osc1.output.connect(0, lineOut.input, 0); + osc2.output.connect(0, lineOut.input, 1); + + // Arrange the faders in a stack. + setLayout(new GridLayout(0, 1)); + + JPanel infoPanel = new JPanel(); + infoPanel.setLayout(new GridLayout(0, 1)); + infoPanel.add(new JLabel("About: " + synth, SwingConstants.CENTER)); + infoPanel.add(new JLabel("From: http://www.softsynth.com/", SwingConstants.CENTER)); + infoPanel.add(new JLabel("(C) 1997-2011 Mobileer Inc", SwingConstants.CENTER)); + add(infoPanel); + + // Set the minimum, current and maximum values for the port. + lag.output.connect(osc1.amplitude); + lag.output.connect(osc2.amplitude); + lag.input.setup(0.001, 0.5, 1.0); + lag.time.set(0.1); + lag.input.setName("Amplitude"); + add(PortControllerFactory.createExponentialPortSlider(lag.input)); + + osc1.frequency.setup(50.0, 300.0, 3000.0); + osc1.frequency.setName("Frequency (Left)"); + add(PortControllerFactory.createExponentialPortSlider(osc1.frequency)); + osc2.frequency.setup(50.0, 302.0, 3000.0); + osc2.frequency.setName("Frequency (Right)"); + add(PortControllerFactory.createExponentialPortSlider(osc2.frequency)); + validate(); + } + + @Override + public void start() { + // Start synthesizer using default stereo output at 44100 Hz. + synth.start(); + // We only need to start the LineOut. It will pull data from the + // oscillator. + lineOut.start(); + } + + @Override + public void stop() { + synth.stop(); + } + + /* Can be run as either an application or as an applet. */ + public static void main(String[] args) { + AboutJSyn applet = new AboutJSyn(); + JAppletFrame frame = new JAppletFrame("About JSyn", applet); + frame.setSize(440, 300); + frame.setVisible(true); + frame.test(); + } + +} diff --git a/src/main/java/com/jsyn/apps/InstrumentTester.java b/src/main/java/com/jsyn/apps/InstrumentTester.java new file mode 100644 index 0000000..2505759 --- /dev/null +++ b/src/main/java/com/jsyn/apps/InstrumentTester.java @@ -0,0 +1,210 @@ +/* + * Copyright 2012 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.apps; + +import java.awt.BorderLayout; +import java.io.IOException; + +import javax.sound.midi.MidiDevice; +import javax.sound.midi.MidiMessage; +import javax.sound.midi.MidiUnavailableException; +import javax.sound.midi.Receiver; +import javax.swing.JApplet; + +import com.jsyn.JSyn; +import com.jsyn.Synthesizer; +import com.jsyn.devices.javasound.MidiDeviceTools; +import com.jsyn.instruments.JSynInstrumentLibrary; +import com.jsyn.midi.MessageParser; +import com.jsyn.swing.InstrumentBrowser; +import com.jsyn.swing.JAppletFrame; +import com.jsyn.swing.PresetSelectionListener; +import com.jsyn.swing.SoundTweaker; +import com.jsyn.unitgen.LineOut; +import com.jsyn.unitgen.UnitSource; +import com.jsyn.unitgen.UnitVoice; +import com.jsyn.util.PolyphonicInstrument; +import com.jsyn.util.VoiceDescription; +import com.softsynth.math.AudioMath; +import com.softsynth.shared.time.TimeStamp; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Let the user select an instrument using the InstrumentBrowser and play + * them using the ASCII keyboard or with MIDI. + * Sound parameters can be tweaked using faders. + * + * @author Phil Burk (C) 2012 Mobileer Inc + */ +public class InstrumentTester extends JApplet { + + private static final Logger LOGGER = LoggerFactory.getLogger(InstrumentTester.class); + private static final long serialVersionUID = -2704222221111608377L; + + private Synthesizer synth; + private LineOut lineOut; + private SoundTweaker tweaker; + protected PolyphonicInstrument instrument; + private MyParser messageParser; + + class MyParser extends MessageParser { + + @Override + public void controlChange(int channel, int index, int value) { + } + + @Override + public void noteOff(int channel, int noteNumber, int velocity) { + instrument.noteOff(noteNumber, synth.createTimeStamp()); + } + + @Override + public void noteOn(int channel, int noteNumber, int velocity) { + double frequency = AudioMath.pitchToFrequency(noteNumber); + double amplitude = velocity / (4 * 128.0); + TimeStamp timeStamp = synth.createTimeStamp(); + instrument.noteOn(noteNumber, frequency, amplitude, timeStamp); + } + + } + + // Write a Receiver to get the messages from a Transmitter. + class CustomReceiver implements Receiver { + @Override + public void close() { + System.out.print("Closed."); + } + + @Override + public void send(MidiMessage message, long timeStamp) { + byte[] bytes = message.getMessage(); + messageParser.parse(bytes); + } + } + + public int setupMidiKeyboard() throws MidiUnavailableException, IOException, InterruptedException { + messageParser = new MyParser(); + + int result = 2; + MidiDevice keyboard = MidiDeviceTools.findKeyboard(); + Receiver receiver = new CustomReceiver(); + // Just use default synthesizer. + if (keyboard != null) { + // If you forget to open them you will hear no sound. + keyboard.open(); + // Put the receiver in the transmitter. + // This gives fairly low latency playing. + keyboard.getTransmitter().setReceiver(receiver); + LOGGER.debug("Play MIDI keyboard: " + keyboard.getDeviceInfo().getDescription()); + result = 0; + } else { + LOGGER.debug("Could not find a keyboard."); + } + return result; + } + + @Override + public void init() { + setLayout(new BorderLayout()); + + synth = JSyn.createSynthesizer(); + synth.add(lineOut = new LineOut()); + + InstrumentBrowser browser = new InstrumentBrowser(new JSynInstrumentLibrary()); + browser.addPresetSelectionListener(new PresetSelectionListener() { + + @Override + public void presetSelected(VoiceDescription voiceDescription, int presetIndex) { + UnitVoice[] voices = new UnitVoice[8]; + for (int i = 0; i < voices.length; i++) { + voices[i] = voiceDescription.createUnitVoice(); + } + instrument = new PolyphonicInstrument(voices); + synth.add(instrument); + instrument.usePreset(presetIndex, synth.createTimeStamp()); + String title = voiceDescription.getVoiceClassName() + ": " + + voiceDescription.getPresetNames()[presetIndex]; + useSource(instrument, title); + } + }); + add(browser, BorderLayout.NORTH); + + try { + setupMidiKeyboard(); + } catch (MidiUnavailableException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (InterruptedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + validate(); + } + + private void useSource(UnitSource voice, String title) { + + lineOut.input.disconnectAll(0); + lineOut.input.disconnectAll(1); + + // Connect the source to both left and right output. + voice.getOutput().connect(0, lineOut.input, 0); + voice.getOutput().connect(0, lineOut.input, 1); + + if (tweaker != null) { + remove(tweaker); + } + try { + if (synth.isRunning()) { + synth.sleepFor(0.1); + } + } catch (InterruptedException e) { + e.printStackTrace(); + } + tweaker = new SoundTweaker(synth, title, voice); + add(tweaker, BorderLayout.CENTER); + validate(); + } + + @Override + public void start() { + // Start synthesizer using default stereo output at 44100 Hz. + synth.start(); + // We only need to start the LineOut. It will pull data from the + // oscillator. + lineOut.start(); + } + + @Override + public void stop() { + synth.stop(); + } + + /* Can be run as either an application or as an applet. */ + public static void main(String[] args) { + InstrumentTester applet = new InstrumentTester(); + JAppletFrame frame = new JAppletFrame("InstrumentTester", applet); + frame.setSize(600, 800); + frame.setVisible(true); + frame.test(); + } + +} diff --git a/src/main/java/com/jsyn/data/AudioSample.java b/src/main/java/com/jsyn/data/AudioSample.java new file mode 100644 index 0000000..dcbbae5 --- /dev/null +++ b/src/main/java/com/jsyn/data/AudioSample.java @@ -0,0 +1,108 @@ +/* + * Copyright 2010 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.data; + +import java.util.ArrayList; + +/** + * Base class for FloatSample and ShortSample. + * + * @author Phil Burk (C) 2010 Mobileer Inc + */ +public abstract class AudioSample extends SequentialDataCommon { + protected int numFrames; + protected int channelsPerFrame = 1; + private double frameRate = 44100.0; + private double pitch; + private ArrayList markers; + + public abstract void allocate(int numFrames, int channelsPerFrame); + + @Override + public double getRateScaler(int index, double synthesisRate) { + return 1.0; + } + + public double getFrameRate() { + return frameRate; + } + + public void setFrameRate(double f) { + this.frameRate = f; + } + + @Override + public int getNumFrames() { + return numFrames; + } + + @Override + public int getChannelsPerFrame() { + return channelsPerFrame; + } + + public void setChannelsPerFrame(int channelsPerFrame) { + this.channelsPerFrame = channelsPerFrame; + } + + /** + * Set the recorded pitch as a fractional MIDI semitone value where 60 is Middle C. + * + * @param pitch + */ + public void setPitch(double pitch) { + this.pitch = pitch; + } + + public double getPitch() { + return pitch; + } + + public int getMarkerCount() { + if (markers == null) + return 0; + else + return markers.size(); + } + + public SampleMarker getMarker(int index) { + if (markers == null) + return null; + else + return markers.get(index); + } + + /** + * Add a marker that will be stored sorted by position. This is normally used internally by the + * SampleLoader. + * + * @param marker + */ + public void addMarker(SampleMarker marker) { + if (markers == null) + markers = new ArrayList(); + int idx = markers.size(); + for (int k = 0; k < markers.size(); k++) { + SampleMarker cue = markers.get(k); + if (cue.position > marker.position) { + idx = k; + break; + } + } + markers.add(idx, marker); + } +} diff --git a/src/main/java/com/jsyn/data/DoubleTable.java b/src/main/java/com/jsyn/data/DoubleTable.java new file mode 100644 index 0000000..ca64c94 --- /dev/null +++ b/src/main/java/com/jsyn/data/DoubleTable.java @@ -0,0 +1,109 @@ +/* + * Copyright 2010 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.data; + +import com.jsyn.exceptions.ChannelMismatchException; + +/** + * Evaluate a Function by interpolating between the values in a table. This can be used for + * wavetable lookup or waveshaping. + * + * @author Phil Burk (C) 2010 Mobileer Inc + */ +public class DoubleTable implements Function { + private double[] table; + + public DoubleTable(int numFrames) { + allocate(numFrames); + } + + public DoubleTable(double[] data) { + allocate(data.length); + write(data); + } + + public DoubleTable(ShortSample shortSample) { + if (shortSample.getChannelsPerFrame() != 1) { + throw new ChannelMismatchException("DoubleTable can only be built from mono samples."); + } + short[] buffer = new short[256]; + int framesLeft = shortSample.getNumFrames(); + allocate(framesLeft); + int cursor = 0; + while (framesLeft > 0) { + int numTransfer = framesLeft; + if (numTransfer > buffer.length) { + numTransfer = buffer.length; + } + shortSample.read(cursor, buffer, 0, numTransfer); + write(cursor, buffer, 0, numTransfer); + cursor += numTransfer; + framesLeft -= numTransfer; + } + } + + public void allocate(int numFrames) { + table = new double[numFrames]; + } + + public int length() { + return table.length; + } + + public void write(double[] data) { + write(0, data, 0, data.length); + } + + public void write(int startFrame, short[] data, int startIndex, int numFrames) { + for (int i = 0; i < numFrames; i++) { + table[startFrame + i] = data[startIndex + i] * (1.0 / 32768.0); + } + } + + public void write(int startFrame, double[] data, int startIndex, int numFrames) { + for (int i = 0; i < numFrames; i++) { + table[startFrame + i] = data[startIndex + i]; + } + } + + /** + * Treat the double array as a lookup table with a domain of -1.0 to 1.0. If the input is out of + * range then the output will clip to the end values. + * + * @param input + * @return interpolated value from table + */ + @Override + public double evaluate(double input) { + double interp; + if (input < -1.0) { + interp = table[0]; + } else if (input < 1.0) { + double fractionalIndex = (table.length - 1) * (input - (-1.0)) / 2.0; + // We don't need floor() because fractionalIndex >= 0.0 + int index = (int) fractionalIndex; + double fraction = fractionalIndex - index; + + double s1 = table[index]; + double s2 = table[index + 1]; + interp = ((s2 - s1) * fraction) + s1; + } else { + interp = table[table.length - 1]; + } + return interp; + } +} diff --git a/src/main/java/com/jsyn/data/FloatSample.java b/src/main/java/com/jsyn/data/FloatSample.java new file mode 100644 index 0000000..2d8c973 --- /dev/null +++ b/src/main/java/com/jsyn/data/FloatSample.java @@ -0,0 +1,164 @@ +/* + * Copyright 2010 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.data; + +import com.jsyn.unitgen.FixedRateMonoReader; +import com.jsyn.unitgen.FixedRateStereoReader; +import com.jsyn.unitgen.VariableRateMonoReader; +import com.jsyn.unitgen.VariableRateStereoReader; +import com.jsyn.util.SampleLoader; + +/** + * Store multi-channel floating point audio data in an interleaved buffer. The values are stored as + * 32-bit floats. You can play samples using one of the readers, for example VariableRateMonoReader. + * + * @author Phil Burk (C) 2010 Mobileer Inc + * @see SampleLoader + * @see FixedRateMonoReader + * @see FixedRateStereoReader + * @see VariableRateMonoReader + * @see VariableRateStereoReader + */ +public class FloatSample extends AudioSample implements Function { + private float[] buffer; + + public FloatSample() { + } + + /** Constructor for mono samples. */ + public FloatSample(int numFrames) { + this(numFrames, 1); + } + + /** Constructor for mono samples with data. */ + public FloatSample(float[] data) { + this(data.length, 1); + write(data); + } + + /** Constructor for multi-channel samples with data. */ + public FloatSample(float[] data, int channelsPerFrame) { + this(data.length / channelsPerFrame, channelsPerFrame); + write(data); + } + + /** + * Create a silent sample with enough memory to hold the audio data. The number of sample + * numbers in the array will be numFrames*channelsPerFrame. + * + * @param numFrames number of sample groups. A stereo frame contains 2 samples. + * @param channelsPerFrame 1 for mono, 2 for stereo + */ + public FloatSample(int numFrames, int channelsPerFrame) { + allocate(numFrames, channelsPerFrame); + } + + /** + * Allocate memory to hold the audio data. The number of sample numbers in the array will be + * numFrames*channelsPerFrame. + * + * @param numFrames number of sample groups. A stereo frame contains 2 samples. + * @param channelsPerFrame 1 for mono, 2 for stereo + */ + @Override + public void allocate(int numFrames, int channelsPerFrame) { + buffer = new float[numFrames * channelsPerFrame]; + this.numFrames = numFrames; + this.channelsPerFrame = channelsPerFrame; + } + + /** + * Note that in a stereo sample, a frame has two values. + * + * @param startFrame index of frame in the sample + * @param data data to be written + * @param startIndex index of first value in array + * @param numFrames + */ + public void write(int startFrame, float[] data, int startIndex, int numFrames) { + int numSamplesToWrite = numFrames * channelsPerFrame; + int firstSampleIndexToWrite = startFrame * channelsPerFrame; + System.arraycopy(data, startIndex, buffer, firstSampleIndexToWrite, numSamplesToWrite); + } + + /** + * Note that in a stereo sample, a frame has two values. + * + * @param startFrame index of frame in the sample + * @param data array to receive the data from the sample + * @param startIndex index of first location in array to start filling + * @param numFrames + */ + public void read(int startFrame, float[] data, int startIndex, int numFrames) { + int numSamplesToRead = numFrames * channelsPerFrame; + int firstSampleIndexToRead = startFrame * channelsPerFrame; + System.arraycopy(buffer, firstSampleIndexToRead, data, startIndex, numSamplesToRead); + } + + /** + * Write the entire array to the sample. The sample data must have already been allocated with + * enough room to contain the data. + * + * @param data + */ + public void write(float[] data) { + write(0, data, 0, data.length / getChannelsPerFrame()); + } + + public void read(float[] data) { + read(0, data, 0, data.length / getChannelsPerFrame()); + } + + @Override + public double readDouble(int index) { + return buffer[index]; + } + + @Override + public void writeDouble(int index, double value) { + buffer[index] = (float) value; + } + + /** + * Interpolate between two adjacent samples. + * Note that this will only work for mono, single channel samples. + * + * @param fractionalIndex must be >=0 and < (size-1) + */ + public double interpolate(double fractionalIndex) { + int index = (int) fractionalIndex; + float phase = (float) (fractionalIndex - index); + float source = buffer[index]; + float target = buffer[index + 1]; + return ((target - source) * phase) + source; + } + + /** + * Note that this will only work for mono, single channel samples. + */ + @Override + public double evaluate(double input) { + // Input ranges from -1 to +1 + // Map it to range of sample with guard point. + double normalizedInput = (input + 1.0) * 0.5; + // Clip so it does not go out of range of the sample. + if (normalizedInput < 0.0) normalizedInput = 0.0; + else if (normalizedInput > 1.0) normalizedInput = 1.0; + double fractionalIndex = (getNumFrames() - 1.01) * normalizedInput; + return interpolate(fractionalIndex); + } +} diff --git a/src/main/java/com/jsyn/data/Function.java b/src/main/java/com/jsyn/data/Function.java new file mode 100644 index 0000000..c0e6566 --- /dev/null +++ b/src/main/java/com/jsyn/data/Function.java @@ -0,0 +1,35 @@ +/* + * Copyright 2010 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.data; + +import com.jsyn.unitgen.FunctionEvaluator; +import com.jsyn.unitgen.FunctionOscillator; + +/** + * @author Phil Burk (C) 2010 Mobileer Inc + * @see FunctionEvaluator + * @see FunctionOscillator + */ +public interface Function { + /** + * Convert an input value to an output value. + * + * @param input + * @return generated value + */ + public double evaluate(double input); +} diff --git a/src/main/java/com/jsyn/data/HammingWindow.java b/src/main/java/com/jsyn/data/HammingWindow.java new file mode 100644 index 0000000..d8e1238 --- /dev/null +++ b/src/main/java/com/jsyn/data/HammingWindow.java @@ -0,0 +1,41 @@ +/* + * 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.data; + +public class HammingWindow implements SpectralWindow { + private double[] data; + + /** Construct a generalized Hamming Window */ + public HammingWindow(int length, double alpha, double beta) { + data = new double[length]; + double scaler = 2.0 * Math.PI / (length - 1); + for (int i = 0; i < length; i++) { + data[i] = alpha - (beta * (Math.cos(i * scaler))); + } + } + + /** Traditional Hamming Window with alpha = 25/46 and beta = 21/46 */ + public HammingWindow(int length) { + this(length, 25.0 / 46.0, 21.0 / 46.0); + } + + @Override + public double get(int index) { + return data[index]; + } + +} diff --git a/src/main/java/com/jsyn/data/HannWindow.java b/src/main/java/com/jsyn/data/HannWindow.java new file mode 100644 index 0000000..878d07c --- /dev/null +++ b/src/main/java/com/jsyn/data/HannWindow.java @@ -0,0 +1,36 @@ +/* + * Copyright 2013 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.data; + +import com.jsyn.unitgen.SpectralFFT; +import com.jsyn.unitgen.SpectralIFFT; + +/** + * A HammingWindow with alpha and beta = 0.5. + * + * @author Phil Burk (C) 2013 Mobileer Inc + * @see SpectralWindow + * @see SpectralFFT + * @see SpectralIFFT + */ +public class HannWindow extends HammingWindow { + + public HannWindow(int length) { + super(length, 0.5, 0.5); + } + +} diff --git a/src/main/java/com/jsyn/data/SampleMarker.java b/src/main/java/com/jsyn/data/SampleMarker.java new file mode 100644 index 0000000..d3db1d4 --- /dev/null +++ b/src/main/java/com/jsyn/data/SampleMarker.java @@ -0,0 +1,30 @@ +/* + * Copyright 2012 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.data; + +/** + * A marker for an audio sample. + * + * @author (C) 2012 Phil Burk, Mobileer Inc + */ + +public class SampleMarker { + /** Sample frame index. */ + public int position; + public String name; + public String comment; +} diff --git a/src/main/java/com/jsyn/data/SegmentedEnvelope.java b/src/main/java/com/jsyn/data/SegmentedEnvelope.java new file mode 100644 index 0000000..efdfd89 --- /dev/null +++ b/src/main/java/com/jsyn/data/SegmentedEnvelope.java @@ -0,0 +1,125 @@ +/* + * Copyright 2010 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.data; + +import com.jsyn.unitgen.EnvelopeDAHDSR; +import com.jsyn.unitgen.VariableRateMonoReader; + +/** + * Store an envelope as a series of line segments. Each line is described as a duration and a target + * value. The envelope can be played using a {@link VariableRateMonoReader}. Here is an example that + * generates an envelope that looks like a traditional ADSR envelope. + * + *

+ * 
+ * 	// Create an amplitude envelope and fill it with data.
+ * 	double[] ampData = {
+ * 		0.02, 0.9, // duration,value pair 0, "attack"
+ * 		0.10, 0.5, // pair 1, "decay"
+ * 		0.50, 0.0  // pair 2, "release"
+ * 	};
+ * 	SegmentedEnvelope ampEnvelope = new SegmentedEnvelope( ampData );
+ * 	
+ * 	// Hang at end of decay segment to provide a "sustain" segment.
+ * 	ampEnvelope.setSustainBegin( 1 );
+ * 	ampEnvelope.setSustainEnd( 1 );
+ * 	
+ * 	// Play the envelope using queueOn so that it uses the sustain and release information.
+ * 	synth.add( ampEnv = new VariableRateMonoReader() );
+ * 	ampEnv.dataQueue.queueOn( ampEnvelope );
+ * 
+ * 
+ * + * As an alternative you could use an {@link EnvelopeDAHDSR}. + * + * @author Phil Burk (C) 2010 Mobileer Inc + * @see VariableRateMonoReader + * @see EnvelopeDAHDSR + */ +public class SegmentedEnvelope extends SequentialDataCommon { + private double[] buffer; + + public SegmentedEnvelope(int maxFrames) { + allocate(maxFrames); + } + + public SegmentedEnvelope(double[] pairs) { + this(pairs.length / 2); + write(pairs); + } + + public void allocate(int maxFrames) { + buffer = new double[maxFrames * 2]; + this.maxFrames = maxFrames; + this.numFrames = 0; + } + + /** + * Write frames of envelope data. A frame consists of a duration and a value. + * + * @param startFrame Index of frame in envelope to write to. + * @param data Pairs of duration and value. + * @param startIndex Index of frame in data[] to read from. + * @param numToWrite Number of frames (pairs) to write. + */ + public void write(int startFrame, double[] data, int startIndex, int numToWrite) { + System.arraycopy(data, startIndex * 2, buffer, startFrame * 2, numToWrite * 2); + if ((startFrame + numToWrite) > numFrames) { + numFrames = startFrame + numToWrite; + } + } + + public void read(int startFrame, double[] data, int startIndex, int numToRead) { + System.arraycopy(buffer, startFrame * 2, data, startIndex * 2, numToRead * 2); + } + + public void write(double[] data) { + write(0, data, 0, data.length / 2); + } + + public void read(double[] data) { + read(0, data, 0, data.length / 2); + } + + /** Read the value of an envelope, not the duration. */ + @Override + public double readDouble(int index) { + return buffer[(index * 2) + 1]; + } + + @Override + public void writeDouble(int index, double value) { + buffer[(index * 2) + 1] = value; + if ((index + 1) > numFrames) { + numFrames = index + 1; + } + } + + @Override + public double getRateScaler(int index, double synthesisPeriod) { + double duration = buffer[index * 2]; + if (duration < synthesisPeriod) { + duration = synthesisPeriod; + } + return 1.0 / duration; + } + + @Override + public int getChannelsPerFrame() { + return 1; + } +} diff --git a/src/main/java/com/jsyn/data/SequentialData.java b/src/main/java/com/jsyn/data/SequentialData.java new file mode 100644 index 0000000..f567493 --- /dev/null +++ b/src/main/java/com/jsyn/data/SequentialData.java @@ -0,0 +1,96 @@ +/* + * Copyright 2010 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.data; + +import com.jsyn.unitgen.FixedRateMonoReader; +import com.jsyn.unitgen.FixedRateMonoWriter; +import com.jsyn.unitgen.FixedRateStereoReader; +import com.jsyn.unitgen.FixedRateStereoWriter; +import com.jsyn.unitgen.VariableRateMonoReader; +import com.jsyn.unitgen.VariableRateStereoReader; + +/** + * Interface for objects that can be read and/or written by index. The index is not stored + * internally so they can be shared by multiple readers. + * + * @author Phil Burk (C) 2010 Mobileer Inc + * @see FixedRateMonoReader + * @see FixedRateStereoReader + * @see FixedRateMonoWriter + * @see FixedRateStereoWriter + * @see VariableRateMonoReader + * @see VariableRateStereoReader + */ +public interface SequentialData { + /** + * Write a value at the given index. + * + * @param index sample index is ((frameIndex * channelsPerFrame) + channelIndex) + * @param value the value to be written + */ + void writeDouble(int index, double value); + + /** + * Read a value from the sample independently from the internal storage format. + * + * @param index sample index is ((frameIndex * channelsPerFrame) + channelIndex) + */ + double readDouble(int index); + + /*** + * @return Beginning of sustain loop or -1 if no loop. + */ + public int getSustainBegin(); + + /** + * SustainEnd value is the frame index of the frame just past the end of the loop. The number of + * frames included in the loop is (SustainEnd - SustainBegin). + * + * @return End of sustain loop or -1 if no loop. + */ + public int getSustainEnd(); + + /*** + * @return Beginning of release loop or -1 if no loop. + */ + public int getReleaseBegin(); + + /*** + * @return End of release loop or -1 if no loop. + */ + public int getReleaseEnd(); + + /** + * Get rate to play the data. In an envelope this correspond to the inverse of the frame + * duration and would vary frame to frame. For an audio sample it is 1.0. + * + * @param index + * @param synthesisRate + * @return rate to scale the playback speed. + */ + double getRateScaler(int index, double synthesisRate); + + /** + * @return For a stereo sample, return 2. + */ + int getChannelsPerFrame(); + + /** + * @return The number of valid frames that can be read. + */ + int getNumFrames(); +} diff --git a/src/main/java/com/jsyn/data/SequentialDataCommon.java b/src/main/java/com/jsyn/data/SequentialDataCommon.java new file mode 100644 index 0000000..5cc51df --- /dev/null +++ b/src/main/java/com/jsyn/data/SequentialDataCommon.java @@ -0,0 +1,136 @@ +/* + * Copyright 2010 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.data; + +/** + * Abstract base class for envelopes and samples that adds sustain and release loops. + * + * @author Phil Burk (C) 2010 Mobileer Inc + */ +public abstract class SequentialDataCommon implements SequentialData { + protected int numFrames; + protected int maxFrames; + private int sustainBegin = -1; + private int sustainEnd = -1; + private int releaseBegin = -1; + private int releaseEnd = -1; + + @Override + public abstract void writeDouble(int index, double value); + + @Override + public abstract double readDouble(int index); + + @Override + public abstract double getRateScaler(int index, double synthesisRate); + + @Override + public abstract int getChannelsPerFrame(); + + /** + * @return Maximum number of frames of data. + */ + public int getMaxFrames() { + return maxFrames; + } + + /** + * Set number of frames of data. Input will be clipped to maxFrames. This is useful when + * changing the contents of a sample or envelope. + */ + public void setNumFrames(int numFrames) { + if (numFrames > maxFrames) + numFrames = maxFrames; + this.numFrames = numFrames; + } + + @Override + public int getNumFrames() { + return numFrames; + } + + // JavaDocs will be copied from SequentialData + + @Override + public int getSustainBegin() { + return this.sustainBegin; + } + + @Override + public int getSustainEnd() { + return this.sustainEnd; + } + + @Override + public int getReleaseBegin() { + return this.releaseBegin; + } + + @Override + public int getReleaseEnd() { + return this.releaseEnd; + } + + /** + * Set beginning of a sustain loop. When UnitDataQueuePort.queueOn() is called, + * if the loop is set then the attack portion will be queued followed by this sustain + * region using queueLoop(). + * The number of frames in the loop will be (SustainEnd - SustainBegin). + *

+ * For a steady sustain level, like in an ADSR envelope, set SustainBegin and + * SustainEnd to the same frame. + *

+ * For a sustain that is modulated, include two or more frames in the loop. + * + * @param sustainBegin + */ + public void setSustainBegin(int sustainBegin) { + this.sustainBegin = sustainBegin; + } + + /** + * SustainEnd value is the frame index of the frame just past the end of the loop. + * The number of frames included in the loop is (SustainEnd - SustainBegin). + * + * @param sustainEnd + */ + public void setSustainEnd(int sustainEnd) { + this.sustainEnd = sustainEnd; + } + + /** + * The release loop behaves like the sustain loop but it is triggered + * by UnitDataQueuePort.queueOff(). + * + * @param releaseBegin + */ + public void setReleaseBegin(int releaseBegin) { + this.releaseBegin = releaseBegin; + } + + /** + * ReleaseEnd value is the frame index of the frame just past the end of the loop. + * The number of frames included in the loop is (ReleaseEnd - ReleaseBegin). + * + * @param releaseEnd + */ + + public void setReleaseEnd(int releaseEnd) { + this.releaseEnd = releaseEnd; + } + +} diff --git a/src/main/java/com/jsyn/data/ShortSample.java b/src/main/java/com/jsyn/data/ShortSample.java new file mode 100644 index 0000000..4a4110e --- /dev/null +++ b/src/main/java/com/jsyn/data/ShortSample.java @@ -0,0 +1,123 @@ +/* + * Copyright 2010 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.data; + +import com.jsyn.engine.SynthesisEngine; +import com.jsyn.unitgen.FixedRateMonoReader; +import com.jsyn.unitgen.FixedRateStereoReader; +import com.jsyn.unitgen.VariableRateMonoReader; +import com.jsyn.unitgen.VariableRateStereoReader; +import com.jsyn.util.SampleLoader; + +/** + * Store multi-channel short audio data in an interleaved buffer. + * + * @author Phil Burk (C) 2010 Mobileer Inc + * @see SampleLoader + * @see FixedRateMonoReader + * @see FixedRateStereoReader + * @see VariableRateMonoReader + * @see VariableRateStereoReader + */ +public class ShortSample extends AudioSample { + private short[] buffer; + + public ShortSample() { + } + + public ShortSample(int numFrames, int channelsPerFrame) { + allocate(numFrames, channelsPerFrame); + } + + /** Constructor for mono samples with data. */ + public ShortSample(short[] data) { + this(data.length, 1); + write(data); + } + + /** Constructor for multi-channel samples with data. */ + public ShortSample(short[] data, int channelsPerFrame) { + this(data.length / channelsPerFrame, channelsPerFrame); + write(data); + } + + @Override + public void allocate(int numFrames, int channelsPerFrame) { + buffer = new short[numFrames * channelsPerFrame]; + this.numFrames = numFrames; + this.channelsPerFrame = channelsPerFrame; + } + + /** + * Note that in a stereo sample, a frame has two values. + * + * @param startFrame index of frame in the sample + * @param data data to be written + * @param startIndex index of first value in array + * @param numFrames + */ + public void write(int startFrame, short[] data, int startIndex, int numFrames) { + int numSamplesToWrite = numFrames * channelsPerFrame; + int firstSampleIndexToWrite = startFrame * channelsPerFrame; + System.arraycopy(data, startIndex, buffer, firstSampleIndexToWrite, numSamplesToWrite); + } + + /** + * Note that in a stereo sample, a frame has two values. + * + * @param startFrame index of frame in the sample + * @param data array to receive the data from the sample + * @param startIndex index of first location in array to start filling + * @param numFrames + */ + public void read(int startFrame, short[] data, int startIndex, int numFrames) { + int numSamplesToRead = numFrames * channelsPerFrame; + int firstSampleIndexToRead = startFrame * channelsPerFrame; + System.arraycopy(buffer, firstSampleIndexToRead, data, startIndex, numSamplesToRead); + } + + public void write(short[] data) { + write(0, data, 0, data.length); + } + + public void read(short[] data) { + read(0, data, 0, data.length); + } + + public short readShort(int index) { + return buffer[index]; + } + + public void writeShort(int index, short value) { + buffer[index] = value; + } + + /** Read a sample converted to a double in the range -1.0 to almost 1.0. */ + @Override + public double readDouble(int index) { + return SynthesisEngine.convertShortToDouble(buffer[index]); + } + + /** + * Write a double that will be clipped to the range -1.0 to almost 1.0 and converted to a short. + */ + @Override + public void writeDouble(int index, double value) { + buffer[index] = SynthesisEngine.convertDoubleToShort(value); + } + +} diff --git a/src/main/java/com/jsyn/data/SpectralWindow.java b/src/main/java/com/jsyn/data/SpectralWindow.java new file mode 100644 index 0000000..0fcfac4 --- /dev/null +++ b/src/main/java/com/jsyn/data/SpectralWindow.java @@ -0,0 +1,21 @@ +/* + * 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.data; + +public interface SpectralWindow { + public double get(int index); +} diff --git a/src/main/java/com/jsyn/data/SpectralWindowFactory.java b/src/main/java/com/jsyn/data/SpectralWindowFactory.java new file mode 100644 index 0000000..01cced6 --- /dev/null +++ b/src/main/java/com/jsyn/data/SpectralWindowFactory.java @@ -0,0 +1,55 @@ +/* + * Copyright 2013 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.data; + +import com.jsyn.unitgen.SpectralFFT; +import com.jsyn.unitgen.SpectralFilter; +import com.jsyn.unitgen.SpectralIFFT; + +/** + * Create shared windows as needed for use with FFTs and IFFTs. + * + * @author Phil Burk (C) 2013 Mobileer Inc + * @see SpectralWindow + * @see SpectralFFT + * @see SpectralIFFT + * @see SpectralFilter + */ +public class SpectralWindowFactory { + private static final int NUM_WINDOWS = 16; + private static final int MIN_SIZE_LOG_2 = 2; + private static HammingWindow[] hammingWindows = new HammingWindow[NUM_WINDOWS]; + private static HannWindow[] hannWindows = new HannWindow[NUM_WINDOWS]; + + /** @return a shared standard HammingWindow */ + public static HammingWindow getHammingWindow(int sizeLog2) { + int index = sizeLog2 - MIN_SIZE_LOG_2; + if (hammingWindows[index] == null) { + hammingWindows[index] = new HammingWindow(1 << sizeLog2); + } + return hammingWindows[index]; + } + + /** @return a shared HannWindow */ + public static HannWindow getHannWindow(int sizeLog2) { + int index = sizeLog2 - MIN_SIZE_LOG_2; + if (hannWindows[index] == null) { + hannWindows[index] = new HannWindow(1 << sizeLog2); + } + return hannWindows[index]; + } +} diff --git a/src/main/java/com/jsyn/data/Spectrum.java b/src/main/java/com/jsyn/data/Spectrum.java new file mode 100644 index 0000000..66e4ee4 --- /dev/null +++ b/src/main/java/com/jsyn/data/Spectrum.java @@ -0,0 +1,97 @@ +/* + * Copyright 2013 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.data; + +import com.jsyn.unitgen.SpectralFFT; +import com.jsyn.unitgen.SpectralIFFT; +import com.jsyn.unitgen.SpectralProcessor; + +/** + * Complex spectrum with real and imaginary parts. The frequency associated with each bin of the + * spectrum is: + * + *

+ * frequency = binIndex * sampleRate / size
+ * 
+ * + * Note that the upper half of the spectrum is above the Nyquist frequency. Those frequencies are + * mirrored around the Nyquist frequency. Note that this spectral API is experimental and may change + * at any time. + * + * @author Phil Burk (C) 2013 Mobileer Inc + * @version 016 + * @see SpectralFFT + * @see SpectralIFFT + * @see SpectralProcessor + */ +public class Spectrum { + private double[] real; + private double[] imaginary; + public static final int DEFAULT_SIZE_LOG_2 = 9; + public static final int DEFAULT_SIZE = 1 << DEFAULT_SIZE_LOG_2; + + public Spectrum() { + this(DEFAULT_SIZE); + } + + public Spectrum(int size) { + setSize(size); + } + + public double[] getReal() { + return real; + } + + public double[] getImaginary() { + return imaginary; + } + + /** + * If you change the size of the spectrum then the real and imaginary arrays will be + * reallocated. + * + * @param size + */ + public void setSize(int size) { + if ((real == null) || (real.length != size)) { + real = new double[size]; + imaginary = new double[size]; + } + } + + public int size() { + return real.length; + } + + /** + * Copy this spectrum to another spectrum of the same length. + * + * @param destination + */ + public void copyTo(Spectrum destination) { + assert (size() == destination.size()); + System.arraycopy(real, 0, destination.real, 0, real.length); + System.arraycopy(imaginary, 0, destination.imaginary, 0, imaginary.length); + } + + public void clear() { + for (int i = 0; i < real.length; i++) { + real[i] = 0.0; + imaginary[i] = 0.0; + } + } +} diff --git a/src/main/java/com/jsyn/devices/AudioDeviceFactory.java b/src/main/java/com/jsyn/devices/AudioDeviceFactory.java new file mode 100644 index 0000000..612c81d --- /dev/null +++ b/src/main/java/com/jsyn/devices/AudioDeviceFactory.java @@ -0,0 +1,93 @@ +/* + * Copyright 2010 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.devices; + +import com.jsyn.util.JavaTools; + +/** + * Create a device appropriate for the platform. + * + * @author Phil Burk (C) 2010 Mobileer Inc + */ +public class AudioDeviceFactory { + private static AudioDeviceManager instance; + + /** + * Use a custom device interface. Overrides the selection of a default device manager. + * + * @param instance + */ + public static void setInstance(AudioDeviceManager instance) { + AudioDeviceFactory.instance = instance; + } + + /** + * Try to load JPortAudio or JavaSound devices. + * + * @return A device supported on this platform. + */ + public static AudioDeviceManager createAudioDeviceManager() { + return createAudioDeviceManager(false); + } + + /** + * Try to load JPortAudio or JavaSound devices. + * + * @param preferJavaSound if true then try to create a JavaSound manager before other types. + * @return A device supported on this platform. + */ + public static AudioDeviceManager createAudioDeviceManager(boolean preferJavaSound) { + if (preferJavaSound) { + tryJavaSound(); + tryJPortAudio(); + } else { + tryJPortAudio(); + tryJavaSound(); + } + return instance; + } + + private static void tryJavaSound() { + if (instance == null) { + try { + @SuppressWarnings("unchecked") + Class clazz = JavaTools.loadClass( + "com.jsyn.devices.javasound.JavaSoundAudioDevice", false); + if (clazz != null) { + instance = clazz.newInstance(); + } + } catch (Throwable e) { + System.err.println("Could not load JavaSound device. " + e); + } + } + } + + private static void tryJPortAudio() { + if (instance == null) { + try { + if (JavaTools.loadClass("com.portaudio.PortAudio", false) != null) { + instance = (AudioDeviceManager) JavaTools.loadClass( + "com.jsyn.devices.jportaudio.JPortAudioDevice").newInstance(); + } + + } catch (Throwable e) { + System.err.println("Could not load JPortAudio device. " + e); + } + } + } + +} diff --git a/src/main/java/com/jsyn/devices/AudioDeviceInputStream.java b/src/main/java/com/jsyn/devices/AudioDeviceInputStream.java new file mode 100644 index 0000000..a3d1854 --- /dev/null +++ b/src/main/java/com/jsyn/devices/AudioDeviceInputStream.java @@ -0,0 +1,31 @@ +/* + * 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.devices; + +import com.jsyn.io.AudioInputStream; + +public interface AudioDeviceInputStream extends AudioInputStream { + /** Start the input device. */ + public void start(); + + public void stop(); + + /** + * @return Estimated latency in seconds. + */ + public double getLatency(); +} diff --git a/src/main/java/com/jsyn/devices/AudioDeviceManager.java b/src/main/java/com/jsyn/devices/AudioDeviceManager.java new file mode 100644 index 0000000..ac8d47c --- /dev/null +++ b/src/main/java/com/jsyn/devices/AudioDeviceManager.java @@ -0,0 +1,120 @@ +/* + * Copyright 2010 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.devices; + +/** + * Interface for an audio system. This may be implemented using JavaSound, or a native device + * wrapper. + * + * @author Phil Burk (C) 2010 Mobileer Inc + */ +public interface AudioDeviceManager { + /** + * Pass this value to the start method to request the default device ID. + */ + public final static int USE_DEFAULT_DEVICE = -1; + + /** + * @return The number of devices available. + */ + public int getDeviceCount(); + + /** + * Get the name of an audio device. + * + * @param deviceID An index between 0 to deviceCount-1. + * @return A name that can be shown to the user. + */ + public String getDeviceName(int deviceID); + + /** + * @return A name of the device manager that can be shown to the user. + */ + public String getName(); + + /** + * The user can generally select a default device using a control panel that is part of the + * operating system. + * + * @return The ID for the input device that the user has selected as the default. + */ + public int getDefaultInputDeviceID(); + + /** + * The user can generally select a default device using a control panel that is part of the + * operating system. + * + * @return The ID for the output device that the user has selected as the default. + */ + public int getDefaultOutputDeviceID(); + + /** + * @param deviceID + * @return The maximum number of channels that the device will support. + */ + public int getMaxInputChannels(int deviceID); + + /** + * @param deviceID An index between 0 to numDevices-1. + * @return The maximum number of channels that the device will support. + */ + public int getMaxOutputChannels(int deviceID); + + /** + * This the lowest latency that the device can support reliably. It should be used for + * applications that require low latency such as live processing of guitar signals. + * + * @param deviceID An index between 0 to numDevices-1. + * @return Latency in seconds. + */ + public double getDefaultLowInputLatency(int deviceID); + + /** + * This the highest latency that the device can support. High latency is recommended for + * applications that are not time critical, such as recording. + * + * @param deviceID An index between 0 to numDevices-1. + * @return Latency in seconds. + */ + public double getDefaultHighInputLatency(int deviceID); + + public double getDefaultLowOutputLatency(int deviceID); + + public double getDefaultHighOutputLatency(int deviceID); + + /** + * Set latency in seconds for the audio device. If set to zero then the DefaultLowLatency value + * for the device will be used. This is just a suggestion that will be used when the + * AudioDeviceInputStream is started. + **/ + public int setSuggestedInputLatency(double latency); + + public int setSuggestedOutputLatency(double latency); + + /** + * Create a stream that can be used internally by JSyn for outputting audio data. Applications + * should not call this directly. + */ + AudioDeviceOutputStream createOutputStream(int deviceID, int frameRate, int numOutputChannels); + + /** + * Create a stream that can be used internally by JSyn for acquiring audio input data. + * Applications should not call this directly. + */ + AudioDeviceInputStream createInputStream(int deviceID, int frameRate, int numInputChannels); + +} diff --git a/src/main/java/com/jsyn/devices/AudioDeviceOutputStream.java b/src/main/java/com/jsyn/devices/AudioDeviceOutputStream.java new file mode 100644 index 0000000..5c17efb --- /dev/null +++ b/src/main/java/com/jsyn/devices/AudioDeviceOutputStream.java @@ -0,0 +1,30 @@ +/* + * 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.devices; + +import com.jsyn.io.AudioOutputStream; + +public interface AudioDeviceOutputStream extends AudioOutputStream { + public void start(); + + public void stop(); + + /** + * @return Estimated latency in seconds. + */ + public double getLatency(); +} diff --git a/src/main/java/com/jsyn/devices/javasound/JavaSoundAudioDevice.java b/src/main/java/com/jsyn/devices/javasound/JavaSoundAudioDevice.java new file mode 100644 index 0000000..75c4a8a --- /dev/null +++ b/src/main/java/com/jsyn/devices/javasound/JavaSoundAudioDevice.java @@ -0,0 +1,432 @@ +/* + * 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.devices.javasound; + +import java.util.ArrayList; + +import javax.sound.sampled.AudioFormat; +import javax.sound.sampled.AudioSystem; +import javax.sound.sampled.DataLine; +import javax.sound.sampled.Line; +import javax.sound.sampled.LineUnavailableException; +import javax.sound.sampled.Mixer; +import javax.sound.sampled.SourceDataLine; +import javax.sound.sampled.TargetDataLine; + +import com.jsyn.devices.AudioDeviceInputStream; +import com.jsyn.devices.AudioDeviceManager; +import com.jsyn.devices.AudioDeviceOutputStream; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Use JavaSound to access the audio hardware. + * + * @author Phil Burk (C) 2009 Mobileer Inc + */ +public class JavaSoundAudioDevice implements AudioDeviceManager { + + private static final Logger LOGGER = LoggerFactory.getLogger(JavaSoundAudioDevice.class); + + private static final int BYTES_PER_SAMPLE = 2; + private static final boolean USE_BIG_ENDIAN = false; + + ArrayList deviceRecords; + private double suggestedOutputLatency = 0.040; + private double suggestedInputLatency = 0.100; + private int defaultInputDeviceID = -1; + private int defaultOutputDeviceID = -1; + + public JavaSoundAudioDevice() { + String osName = System.getProperty("os.name"); + if (osName.contains("Windows")) { + suggestedOutputLatency = 0.08; + LOGGER.info("JSyn: default output latency set to " + + ((int) (suggestedOutputLatency * 1000)) + " msec for " + osName); + } + deviceRecords = new ArrayList(); + sniffAvailableMixers(); + dumpAvailableMixers(); + } + + private void dumpAvailableMixers() { + for (DeviceInfo deviceInfo : deviceRecords) { + LOGGER.debug("" + deviceInfo); + } + } + + /** + * Build device info and determine default devices. + */ + private void sniffAvailableMixers() { + Mixer.Info[] mixers = AudioSystem.getMixerInfo(); + for (int i = 0; i < mixers.length; i++) { + DeviceInfo deviceInfo = new DeviceInfo(); + + deviceInfo.name = mixers[i].getName(); + Mixer mixer = AudioSystem.getMixer(mixers[i]); + + Line.Info[] lines = mixer.getTargetLineInfo(); + deviceInfo.maxInputs = scanMaxChannels(lines); + // Remember first device that supports input. + if ((defaultInputDeviceID < 0) && (deviceInfo.maxInputs > 0)) { + defaultInputDeviceID = i; + } + + lines = mixer.getSourceLineInfo(); + deviceInfo.maxOutputs = scanMaxChannels(lines); + // Remember first device that supports output. + if ((defaultOutputDeviceID < 0) && (deviceInfo.maxOutputs > 0)) { + defaultOutputDeviceID = i; + } + + deviceRecords.add(deviceInfo); + } + } + + private int scanMaxChannels(Line.Info[] lines) { + int maxChannels = 0; + for (Line.Info line : lines) { + if (line instanceof DataLine.Info) { + int numChannels = scanMaxChannels(((DataLine.Info) line)); + if (numChannels > maxChannels) { + maxChannels = numChannels; + } + } + } + return maxChannels; + } + + private int scanMaxChannels(DataLine.Info info) { + int maxChannels = 0; + for (AudioFormat format : info.getFormats()) { + int numChannels = format.getChannels(); + if (numChannels > maxChannels) { + maxChannels = numChannels; + } + } + return maxChannels; + } + + static class DeviceInfo { + String name; + int maxInputs; + int maxOutputs; + + @Override + public String toString() { + return "AudioDevice: " + name + ", max in = " + maxInputs + ", max out = " + maxOutputs; + } + } + + private static class JavaSoundStream { + AudioFormat format; + byte[] bytes; + int frameRate; + int deviceID; + int samplesPerFrame; + + public JavaSoundStream(int deviceID, int frameRate, int samplesPerFrame) { + this.deviceID = deviceID; + this.frameRate = frameRate; + this.samplesPerFrame = samplesPerFrame; + format = new AudioFormat(frameRate, 16, samplesPerFrame, true, USE_BIG_ENDIAN); + } + + Line getDataLine(DataLine.Info info) throws LineUnavailableException { + Line dataLine; + if (deviceID >= 0) { + Mixer.Info[] mixers = AudioSystem.getMixerInfo(); + Mixer mixer = AudioSystem.getMixer(mixers[deviceID]); + dataLine = mixer.getLine(info); + } else { + dataLine = AudioSystem.getLine(info); + } + return dataLine; + } + + int calculateBufferSize(double suggestedOutputLatency) { + int numFrames = (int) (suggestedOutputLatency * frameRate); + return numFrames * samplesPerFrame * BYTES_PER_SAMPLE; + } + + } + + private class JavaSoundOutputStream extends JavaSoundStream implements AudioDeviceOutputStream { + SourceDataLine line; + + public JavaSoundOutputStream(int deviceID, int frameRate, int samplesPerFrame) { + super(deviceID, frameRate, samplesPerFrame); + } + + @Override + public void start() { + DataLine.Info info = new DataLine.Info(SourceDataLine.class, format); + if (!AudioSystem.isLineSupported(info)) { + // Handle the error. + LOGGER.error("JavaSoundOutputStream - not supported." + format); + } else { + try { + line = (SourceDataLine) getDataLine(info); + int bufferSize = calculateBufferSize(suggestedOutputLatency); + line.open(format, bufferSize); + LOGGER.debug("Output buffer size = " + bufferSize + " bytes."); + line.start(); + + } catch (Exception e) { + e.printStackTrace(); + line = null; + } + } + } + + /** Grossly inefficient. Call the array version instead. */ + @Override + public void write(double value) { + double[] buffer = new double[1]; + buffer[0] = value; + write(buffer, 0, 1); + } + + @Override + public void write(double[] buffer) { + write(buffer, 0, buffer.length); + } + + @Override + public void write(double[] buffer, int start, int count) { + // Allocate byte buffer if needed. + if ((bytes == null) || ((bytes.length * 2) < count)) { + bytes = new byte[count * 2]; + } + + // Convert float samples to LittleEndian bytes. + int byteIndex = 0; + for (int i = 0; i < count; i++) { + // Offset before casting so that we can avoid using floor(). + // Also round by adding 0.5 so that very small signals go to zero. + double temp = (32767.0 * buffer[i + start]) + 32768.5; + int sample = ((int) temp) - 32768; + if (sample > Short.MAX_VALUE) { + sample = Short.MAX_VALUE; + } else if (sample < Short.MIN_VALUE) { + sample = Short.MIN_VALUE; + } + bytes[byteIndex++] = (byte) sample; // little end + bytes[byteIndex++] = (byte) (sample >> 8); // big end + } + + line.write(bytes, 0, byteIndex); + } + + @Override + public void stop() { + if (line != null) { + line.stop(); + line.flush(); + line.close(); + line = null; + } else { + new RuntimeException("AudioOutput stop attempted when no line created.") + .printStackTrace(); + } + } + + @Override + public double getLatency() { + if (line == null) { + return 0.0; + } + int numBytes = line.getBufferSize(); + int numFrames = numBytes / (BYTES_PER_SAMPLE * samplesPerFrame); + return ((double) numFrames) / frameRate; + } + + @Override + public void close() { + } + + } + + private class JavaSoundInputStream extends JavaSoundStream implements AudioDeviceInputStream { + TargetDataLine line; + + public JavaSoundInputStream(int deviceID, int frameRate, int samplesPerFrame) { + super(deviceID, frameRate, samplesPerFrame); + } + + @Override + public void start() { + DataLine.Info info = new DataLine.Info(TargetDataLine.class, format); + if (!AudioSystem.isLineSupported(info)) { + // Handle the error. + LOGGER.error("JavaSoundInputStream - not supported." + format); + } else { + try { + line = (TargetDataLine) getDataLine(info); + int bufferSize = calculateBufferSize(suggestedInputLatency); + line.open(format, bufferSize); + LOGGER.debug("Input buffer size = " + bufferSize + " bytes."); + line.start(); + } catch (Exception e) { + e.printStackTrace(); + line = null; + } + } + } + + @Override + public double read() { + double[] buffer = new double[1]; + read(buffer, 0, 1); + return buffer[0]; + } + + @Override + public int read(double[] buffer) { + return read(buffer, 0, buffer.length); + } + + @Override + public int read(double[] buffer, int start, int count) { + // Allocate byte buffer if needed. + if ((bytes == null) || ((bytes.length * 2) < count)) { + bytes = new byte[count * 2]; + } + int bytesRead = line.read(bytes, 0, bytes.length); + + // Convert BigEndian bytes to float samples + int bi = 0; + for (int i = 0; i < count; i++) { + int sample = bytes[bi++] & 0x00FF; // little end + sample = sample + (bytes[bi++] << 8); // big end + buffer[i + start] = sample * (1.0 / 32767.0); + } + return bytesRead / 4; + } + + @Override + public void stop() { + if (line != null) { + line.drain(); + line.close(); + } else { + new RuntimeException("AudioInput stop attempted when no line created.") + .printStackTrace(); + } + } + + @Override + public double getLatency() { + if (line == null) { + return 0.0; + } + int numBytes = line.getBufferSize(); + int numFrames = numBytes / (BYTES_PER_SAMPLE * samplesPerFrame); + return ((double) numFrames) / frameRate; + } + + @Override + public int available() { + return line.available() / BYTES_PER_SAMPLE; + } + + @Override + public void close() { + } + + } + + @Override + public AudioDeviceOutputStream createOutputStream(int deviceID, int frameRate, + int samplesPerFrame) { + return new JavaSoundOutputStream(deviceID, frameRate, samplesPerFrame); + } + + @Override + public AudioDeviceInputStream createInputStream(int deviceID, int frameRate, int samplesPerFrame) { + return new JavaSoundInputStream(deviceID, frameRate, samplesPerFrame); + } + + @Override + public double getDefaultHighInputLatency(int deviceID) { + return 0.300; + } + + @Override + public double getDefaultHighOutputLatency(int deviceID) { + return 0.300; + } + + @Override + public int getDefaultInputDeviceID() { + return defaultInputDeviceID; + } + + @Override + public int getDefaultOutputDeviceID() { + return defaultOutputDeviceID; + } + + @Override + public double getDefaultLowInputLatency(int deviceID) { + return 0.100; + } + + @Override + public double getDefaultLowOutputLatency(int deviceID) { + return 0.100; + } + + @Override + public int getDeviceCount() { + return deviceRecords.size(); + } + + @Override + public String getDeviceName(int deviceID) { + return deviceRecords.get(deviceID).name; + } + + @Override + public int getMaxInputChannels(int deviceID) { + return deviceRecords.get(deviceID).maxInputs; + } + + @Override + public int getMaxOutputChannels(int deviceID) { + return deviceRecords.get(deviceID).maxOutputs; + } + + @Override + public int setSuggestedOutputLatency(double latency) { + suggestedOutputLatency = latency; + return 0; + } + + @Override + public int setSuggestedInputLatency(double latency) { + suggestedInputLatency = latency; + return 0; + } + + @Override + public String getName() { + return "JavaSound"; + } + +} diff --git a/src/main/java/com/jsyn/devices/javasound/MidiDeviceTools.java b/src/main/java/com/jsyn/devices/javasound/MidiDeviceTools.java new file mode 100644 index 0000000..9cff095 --- /dev/null +++ b/src/main/java/com/jsyn/devices/javasound/MidiDeviceTools.java @@ -0,0 +1,86 @@ +/* + * 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.devices.javasound; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.sound.midi.MidiDevice; +import javax.sound.midi.MidiSystem; +import javax.sound.midi.MidiUnavailableException; +import javax.sound.midi.Sequencer; +import javax.sound.midi.Synthesizer; + +public class MidiDeviceTools { + + private static final Logger LOGGER = LoggerFactory.getLogger(MidiDeviceTools.class); + + /** Print the available MIDI Devices. */ + public static void listDevices() { + // Ask the MidiSystem what is available. + MidiDevice.Info[] infos = MidiSystem.getMidiDeviceInfo(); + // Print info about each device. + for (MidiDevice.Info info : infos) { + LOGGER.debug("MIDI Info: " + info.getDescription() + ", " + info.getName() + ", " + + info.getVendor() + ", " + info.getVersion()); + // Get the device for more information. + try { + MidiDevice device = MidiSystem.getMidiDevice(info); + LOGGER.debug(" Device: " + ", #recv = " + device.getMaxReceivers() + + ", #xmit = " + device.getMaxTransmitters() + ", open = " + + device.isOpen() + ", " + device); + } catch (MidiUnavailableException e) { + e.printStackTrace(); + } + } + } + + /** Find a MIDI transmitter that contains text in the name. */ + public static MidiDevice findKeyboard(String text) { + MidiDevice keyboard = null; + // Ask the MidiSystem what is available. + MidiDevice.Info[] infos = MidiSystem.getMidiDeviceInfo(); + // Print info about each device. + for (MidiDevice.Info info : infos) { + try { + MidiDevice device = MidiSystem.getMidiDevice(info); + // Hardware devices are not Synthesizers or Sequencers. + if (!(device instanceof Synthesizer) && !(device instanceof Sequencer)) { + // Is this a transmitter? + // Might be -1 if unlimited. + if (device.getMaxTransmitters() != 0) { + if ((text == null) + || (info.getDescription().toLowerCase() + .contains(text.toLowerCase()))) { + keyboard = device; + LOGGER.debug("Chose: " + info.getDescription()); + break; + } + } + } + } catch (MidiUnavailableException e) { + e.printStackTrace(); + } + } + return keyboard; + } + + public static MidiDevice findKeyboard() { + return findKeyboard(null); + } + +} diff --git a/src/main/java/com/jsyn/devices/jportaudio/JPortAudioDevice.java b/src/main/java/com/jsyn/devices/jportaudio/JPortAudioDevice.java new file mode 100644 index 0000000..15ab9ed --- /dev/null +++ b/src/main/java/com/jsyn/devices/jportaudio/JPortAudioDevice.java @@ -0,0 +1,264 @@ +/* + * 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.devices.jportaudio; + +import com.jsyn.devices.AudioDeviceInputStream; +import com.jsyn.devices.AudioDeviceManager; +import com.jsyn.devices.AudioDeviceOutputStream; +import com.portaudio.BlockingStream; +import com.portaudio.DeviceInfo; +import com.portaudio.HostApiInfo; +import com.portaudio.PortAudio; +import com.portaudio.StreamParameters; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class JPortAudioDevice implements AudioDeviceManager { + + private static final Logger LOGGER = LoggerFactory.getLogger(JPortAudioDevice.class); + + private double suggestedOutputLatency = 0.030; + private double suggestedInputLatency = 0.050; + private static final int FRAMES_PER_BUFFER = 128; + + // static Logger logger = Logger.getLogger( JPortAudioDevice.class.getName() ); + + public JPortAudioDevice() { + PortAudio.initialize(); + } + + @Override + public int getDeviceCount() { + return PortAudio.getDeviceCount(); + } + + @Override + public String getDeviceName(int deviceID) { + DeviceInfo deviceInfo = PortAudio.getDeviceInfo(deviceID); + HostApiInfo hostInfo = PortAudio.getHostApiInfo(deviceInfo.hostApi); + return deviceInfo.name + " - " + hostInfo.name; + } + + @Override + public int getDefaultInputDeviceID() { + return PortAudio.getDefaultInputDevice(); + } + + @Override + public int getDefaultOutputDeviceID() { + return PortAudio.getDefaultOutputDevice(); + } + + @Override + public int getMaxInputChannels(int deviceID) { + if (deviceID < 0) { + deviceID = PortAudio.getDefaultInputDevice(); + } + return PortAudio.getDeviceInfo(deviceID).maxInputChannels; + } + + @Override + public int getMaxOutputChannels(int deviceID) { + if (deviceID < 0) { + deviceID = PortAudio.getDefaultOutputDevice(); + } + return PortAudio.getDeviceInfo(deviceID).maxOutputChannels; + } + + @Override + public double getDefaultLowInputLatency(int deviceID) { + if (deviceID < 0) { + deviceID = PortAudio.getDefaultInputDevice(); + } + return PortAudio.getDeviceInfo(deviceID).defaultLowInputLatency; + } + + @Override + public double getDefaultHighInputLatency(int deviceID) { + if (deviceID < 0) { + deviceID = PortAudio.getDefaultInputDevice(); + } + return PortAudio.getDeviceInfo(deviceID).defaultHighInputLatency; + } + + @Override + public double getDefaultLowOutputLatency(int deviceID) { + if (deviceID < 0) { + deviceID = PortAudio.getDefaultOutputDevice(); + } + return PortAudio.getDeviceInfo(deviceID).defaultLowOutputLatency; + } + + @Override + public double getDefaultHighOutputLatency(int deviceID) { + if (deviceID < 0) { + deviceID = PortAudio.getDefaultOutputDevice(); + } + return PortAudio.getDeviceInfo(deviceID).defaultHighOutputLatency; + } + + @Override + public int setSuggestedOutputLatency(double latency) { + suggestedOutputLatency = latency; + return 0; + } + + @Override + public int setSuggestedInputLatency(double latency) { + suggestedInputLatency = latency; + return 0; + } + + @Override + public AudioDeviceOutputStream createOutputStream(int deviceID, int frameRate, + int samplesPerFrame) { + return new JPAOutputStream(deviceID, frameRate, samplesPerFrame); + } + + @Override + public AudioDeviceInputStream createInputStream(int deviceID, int frameRate, int samplesPerFrame) { + return new JPAInputStream(deviceID, frameRate, samplesPerFrame); + } + + private static class JPAStream { + BlockingStream blockingStream; + float[] floatBuffer = null; + int samplesPerFrame; + + public void close() { + blockingStream.close(); + } + + public void start() { + blockingStream.start(); + } + + public void stop() { + blockingStream.stop(); + } + + } + + private class JPAOutputStream extends JPAStream implements AudioDeviceOutputStream { + + private JPAOutputStream(int deviceID, int frameRate, int samplesPerFrame) { + this.samplesPerFrame = samplesPerFrame; + StreamParameters streamParameters = new StreamParameters(); + streamParameters.channelCount = samplesPerFrame; + if (deviceID < 0) { + deviceID = PortAudio.getDefaultOutputDevice(); + } + streamParameters.device = deviceID; + streamParameters.suggestedLatency = suggestedOutputLatency; + int flags = 0; + LOGGER.debug("Audio output on " + getDeviceName(deviceID)); + blockingStream = PortAudio.openStream(null, streamParameters, frameRate, + FRAMES_PER_BUFFER, flags); + } + + /** Grossly inefficient. Call the array version instead. */ + @Override + public void write(double value) { + double[] buffer = new double[1]; + buffer[0] = value; + write(buffer, 0, 1); + } + + @Override + public void write(double[] buffer) { + write(buffer, 0, buffer.length); + } + + @Override + public void write(double[] buffer, int start, int count) { + // Allocate float buffer if needed. + if ((floatBuffer == null) || (floatBuffer.length < count)) { + floatBuffer = new float[count]; + } + for (int i = 0; i < count; i++) { + + floatBuffer[i] = (float) buffer[i + start]; + } + blockingStream.write(floatBuffer, count / samplesPerFrame); + } + + @Override + public double getLatency() { + return blockingStream.getInfo().outputLatency; + } + } + + private class JPAInputStream extends JPAStream implements AudioDeviceInputStream { + private JPAInputStream(int deviceID, int frameRate, int samplesPerFrame) { + this.samplesPerFrame = samplesPerFrame; + StreamParameters streamParameters = new StreamParameters(); + streamParameters.channelCount = samplesPerFrame; + if (deviceID < 0) { + deviceID = PortAudio.getDefaultInputDevice(); + } + streamParameters.device = deviceID; + streamParameters.suggestedLatency = suggestedInputLatency; + int flags = 0; + LOGGER.debug("Audio input from " + getDeviceName(deviceID)); + blockingStream = PortAudio.openStream(streamParameters, null, frameRate, + FRAMES_PER_BUFFER, flags); + } + + @Override + public double read() { + double[] buffer = new double[1]; + read(buffer, 0, 1); + return buffer[0]; + } + + @Override + public int read(double[] buffer) { + return read(buffer, 0, buffer.length); + } + + @Override + public int read(double[] buffer, int start, int count) { + // Allocate float buffer if needed. + if ((floatBuffer == null) || (floatBuffer.length < count)) { + floatBuffer = new float[count]; + } + blockingStream.read(floatBuffer, count / samplesPerFrame); + + for (int i = 0; i < count; i++) { + + buffer[i + start] = floatBuffer[i]; + } + return count; + } + + @Override + public double getLatency() { + return blockingStream.getInfo().inputLatency; + } + + @Override + public int available() { + return blockingStream.getReadAvailable() * samplesPerFrame; + } + + } + + @Override + public String getName() { + return "JPortAudio"; + } +} diff --git a/src/main/java/com/jsyn/engine/LoadAnalyzer.java b/src/main/java/com/jsyn/engine/LoadAnalyzer.java new file mode 100644 index 0000000..cbf7ed5 --- /dev/null +++ b/src/main/java/com/jsyn/engine/LoadAnalyzer.java @@ -0,0 +1,61 @@ +/* + * 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.engine; + +/** Measure CPU load. */ +public class LoadAnalyzer { + private long stopTime; + private long previousStopTime; + private long startTime; + private double averageTotalTime; + private double averageOnTime; + + protected LoadAnalyzer() { + stopTime = System.nanoTime(); + } + + /** + * Call this when you stop doing something. Ideally all of the time since start() was spent on + * doing something without interruption. + */ + public void stop() { + previousStopTime = stopTime; + stopTime = System.nanoTime(); + long onTime = stopTime - startTime; + long totalTime = stopTime - previousStopTime; + if (totalTime > 0) { + // Recursive averaging filter. + double rate = 0.01; + averageOnTime = (averageOnTime * (1.0 - rate)) + (onTime * rate); + averageTotalTime = (averageTotalTime * (1.0 - rate)) + (totalTime * rate); + } + } + + /** Call this when you start doing something. */ + public void start() { + startTime = System.nanoTime(); + } + + /** Calculate, on average, how much of the time was spent doing something. */ + public double getAverageLoad() { + if (averageTotalTime > 0.0) { + return averageOnTime / averageTotalTime; + } else { + return 0.0; + } + } +} diff --git a/src/main/java/com/jsyn/engine/MultiTable.java b/src/main/java/com/jsyn/engine/MultiTable.java new file mode 100644 index 0000000..6606639 --- /dev/null +++ b/src/main/java/com/jsyn/engine/MultiTable.java @@ -0,0 +1,230 @@ +/* + * 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.engine; + +/* + * Multiple tables of sawtooth data. + * organized by octaves below the Nyquist Rate. + * used to generate band-limited Sawtooth, Impulse, Pulse, Square and Triangle BL waveforms + * +
+ Analysis of octave requirements for tables.
+
+ OctavesIndex    Frequency     Partials
+ 0               N/2  11025      1
+ 1               N/4   5512      2
+ 2               N/8   2756      4
+ 3               N/16  1378      8
+ 4               N/32   689      16
+ 5               N/64   344      32
+ 6               N/128  172      64
+ 7               N/256   86      128
+ 
+ * + * @author Phil Burk (C) 2009 Mobileer Inc + */ +public class MultiTable { + + public final static int NUM_TABLES = 8; + public final static int CYCLE_SIZE = (1 << 10); + + private static MultiTable instance = new MultiTable(NUM_TABLES, CYCLE_SIZE); + private double phaseScalar; + private float[][] tables; // array of array of tables + + /************************************************************************** + * Initialize sawtooth wavetables. Table[0] should contain a pure sine wave. Succeeding tables + * should have increasing numbers of partials. + */ + public MultiTable(int numTables, int cycleSize) { + int tableSize = cycleSize + 1; + + // Allocate array of arrays. + tables = new float[numTables][tableSize]; + + float[] sineTable = tables[0]; + + phaseScalar = (float) (cycleSize * 0.5); + + /* Fill initial sine table with values for -PI to PI. */ + for (int j = 0; j < tableSize; j++) { + sineTable[j] = (float) Math.sin(((((double) j) / (double) cycleSize) * Math.PI * 2.0) + - Math.PI); + } + + /* + * Build each table from scratch and scale partials by raised cosine* to eliminate Gibbs + * effect. + */ + for (int i = 1; i < numTables; i++) { + int numPartials; + double kGibbs; + float[] table = tables[i]; + + /* Add together partials for this table. */ + numPartials = 1 << i; + kGibbs = Math.PI / (2 * numPartials); + for (int k = 0; k < numPartials; k++) { + double ampl, cGibbs; + int sineIndex = 0; + int partial = k + 1; + cGibbs = Math.cos(k * kGibbs); + /* Calculate amplitude for Nth partial */ + ampl = cGibbs * cGibbs / partial; + + for (int j = 0; j < tableSize; j++) { + table[j] += (float) ampl * sineTable[sineIndex]; + sineIndex += partial; + /* Wrap index at end of table.. */ + if (sineIndex >= cycleSize) { + sineIndex -= cycleSize; + } + } + } + } + + /* Normalize after */ + for (int i = 1; i < numTables; i++) { + normalizeArray(tables[i]); + } + } + + /**************************************************************************/ + public static float normalizeArray(float[] fdata) { + float max, val, gain; + int i; + + // determine maximum value. + max = 0.0f; + for (i = 0; i < fdata.length; i++) { + val = Math.abs(fdata[i]); + if (val > max) + max = val; + } + if (max < 0.0000001f) + max = 0.0000001f; + // scale array + gain = 1.0f / max; + for (i = 0; i < fdata.length; i++) + fdata[i] *= gain; + return gain; + } + + /***************************************************************************** + * When the phaseInc maps to the highest level table, then we start interpolating between the + * highest table and the raw sawtooth value (phase). When phaseInc points to highest table: + * flevel = NUM_TABLES - 1 = -1 - log2(pInc); log2(pInc) = - NUM_TABLES pInc = 2**(-NUM_TABLES) + */ + private final static double LOWEST_PHASE_INC_INV = (1 << NUM_TABLES); + + /**************************************************************************/ + /* Phase ranges from -1.0 to +1.0 */ + public double calculateSawtooth(double currentPhase, double positivePhaseIncrement, + double flevel) { + float[] tableBase; + double val; + double hiSam; /* Use when verticalFraction is 1.0 */ + double loSam; /* Use when verticalFraction is 0.0 */ + double sam1, sam2; + + /* Use Phase to determine sampleIndex into table. */ + double findex = ((phaseScalar * currentPhase) + phaseScalar); + // findex is > 0 so we do not need to call floor(). + int sampleIndex = (int) findex; + double horizontalFraction = findex - sampleIndex; + int tableIndex = (int) flevel; + + if (tableIndex > (NUM_TABLES - 2)) { + /* + * Just use top table and mix with arithmetic sawtooth if below lowest frequency. + * Generate new fraction for interpolating between 0.0 and lowest table frequency. + */ + double fraction = positivePhaseIncrement * LOWEST_PHASE_INC_INV; + tableBase = tables[(NUM_TABLES - 1)]; + + /* Get adjacent samples. Assume guard point present. */ + sam1 = tableBase[sampleIndex]; + sam2 = tableBase[sampleIndex + 1]; + /* Interpolate between adjacent samples. */ + loSam = sam1 + (horizontalFraction * (sam2 - sam1)); + + /* Use arithmetic version for low frequencies. */ + /* fraction is 0.0 at 0 Hz */ + val = currentPhase + (fraction * (loSam - currentPhase)); + } else { + + double verticalFraction = flevel - tableIndex; + + if (tableIndex < 0) { + if (tableIndex < -1) // above Nyquist! + { + val = 0.0; + } else { + /* + * At top of supported range, interpolate between 0.0 and first partial. + */ + tableBase = tables[0]; /* Sine wave table. */ + + /* Get adjacent samples. Assume guard point present. */ + sam1 = tableBase[sampleIndex]; + sam2 = tableBase[sampleIndex + 1]; + + /* Interpolate between adjacent samples. */ + hiSam = sam1 + (horizontalFraction * (sam2 - sam1)); + /* loSam = 0.0 */ + // verticalFraction is 0.0 at Nyquist + val = verticalFraction * hiSam; + } + } else { + /* + * Interpolate between adjacent levels to prevent harmonics from popping. + */ + tableBase = tables[tableIndex + 1]; + + /* Get adjacent samples. Assume guard point present. */ + sam1 = tableBase[sampleIndex]; + sam2 = tableBase[sampleIndex + 1]; + + /* Interpolate between adjacent samples. */ + hiSam = sam1 + (horizontalFraction * (sam2 - sam1)); + + /* Get adjacent samples. Assume guard point present. */ + tableBase = tables[tableIndex]; + sam1 = tableBase[sampleIndex]; + sam2 = tableBase[sampleIndex + 1]; + + /* Interpolate between adjacent samples. */ + loSam = sam1 + (horizontalFraction * (sam2 - sam1)); + + val = loSam + (verticalFraction * (hiSam - loSam)); + } + } + return val; + } + + public double convertPhaseIncrementToLevel(double positivePhaseIncrement) { + if (positivePhaseIncrement < 1.0e-30) { + positivePhaseIncrement = 1.0e-30; + } + return -1.0 - (Math.log(positivePhaseIncrement) / Math.log(2.0)); + } + + public static MultiTable getInstance() { + return instance; + } + +} diff --git a/src/main/java/com/jsyn/engine/SynthesisEngine.java b/src/main/java/com/jsyn/engine/SynthesisEngine.java new file mode 100644 index 0000000..30872a8 --- /dev/null +++ b/src/main/java/com/jsyn/engine/SynthesisEngine.java @@ -0,0 +1,700 @@ +/* + * 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.engine; + +import java.util.ArrayList; +import java.util.List; +import java.util.ListIterator; +import java.util.concurrent.CopyOnWriteArrayList; + +import com.jsyn.JSyn; +import com.jsyn.Synthesizer; +import com.jsyn.devices.AudioDeviceFactory; +import com.jsyn.devices.AudioDeviceInputStream; +import com.jsyn.devices.AudioDeviceManager; +import com.jsyn.devices.AudioDeviceOutputStream; +import com.jsyn.unitgen.UnitGenerator; +import com.softsynth.shared.time.ScheduledCommand; +import com.softsynth.shared.time.ScheduledQueue; +import com.softsynth.shared.time.TimeStamp; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +//TODO Resolve problem with HearDAHDSR where "Rate" port.set is not reflected in knob. Engine not running. +//TODO new tutorial and docs on website +//TODO AutoStop on DAHDSR +//TODO Test/example SequentialData queueOn and queueOff + +//TODO Abstract device interface. File device! +//TODO Measure thread switching sync, performance for multi-core synthesis. Use 4 core pro. +//TODO Optimize SineOscillatorPhaseModulated +//TODO More circuits. +//TODO DC blocker +//TODO Swing scope probe UIs, auto ranging + +/** + * Internal implementation of JSyn Synthesizer. The public API is in the Synthesizer interface. This + * class might be used directly internally. + * + * @author Phil Burk (C) 2009 Mobileer Inc + * @see Synthesizer + */ +public class SynthesisEngine implements Synthesizer { + + private static final Logger LOGGER = LoggerFactory.getLogger(SynthesisEngine.class); + + private final static int BLOCKS_PER_BUFFER = 8; + private final static int FRAMES_PER_BUFFER = Synthesizer.FRAMES_PER_BLOCK * BLOCKS_PER_BUFFER; + // I have measured JavaSound taking 1200 msec to close devices. + private static final int MAX_THREAD_STOP_TIME = 2000; + + public static final int DEFAULT_FRAME_RATE = 44100; + + private final AudioDeviceManager audioDeviceManager; + private EngineThread engineThread; + private final ScheduledQueue commandQueue = new ScheduledQueue(); + + private InterleavingBuffer inputBuffer; + private InterleavingBuffer outputBuffer; + private double inverseNyquist; + private long frameCount; + private boolean pullDataEnabled = true; + private boolean useRealTime = true; + private boolean started; + private int frameRate = DEFAULT_FRAME_RATE; + private double framePeriod = 1.0 / frameRate; + + // List of all units added to the synth. + private final ArrayList allUnitList = new ArrayList(); + // List of running units. + private final ArrayList runningUnitList = new ArrayList(); + // List of units stopping because of autoStop. + private final ArrayList stoppingUnitList = new ArrayList(); + + private LoadAnalyzer loadAnalyzer; + // private int numOutputChannels; + // private int numInputChannels; + private final CopyOnWriteArrayList audioTasks = new CopyOnWriteArrayList(); + private double mOutputLatency; + private double mInputLatency; + /** A fraction corresponding to exactly -96 dB. */ + public static final double DB96 = (1.0 / 63095.73444801943); + /** A fraction that is approximately -90.3 dB. Defined as 1 bit of an S16. */ + public static final double DB90 = (1.0 / (1 << 15)); + + public SynthesisEngine(AudioDeviceManager audioDeviceManager) { + this.audioDeviceManager = audioDeviceManager; + } + + public SynthesisEngine() { + this(AudioDeviceFactory.createAudioDeviceManager()); + } + + @Override + public String getVersion() { + return JSyn.VERSION; + } + + @Override + public int getVersionCode() { + return JSyn.VERSION_CODE; + } + + @Override + public String toString() { + return "JSyn " + JSyn.VERSION_TEXT; + } + + public boolean isPullDataEnabled() { + return pullDataEnabled; + } + + /** + * If set true then audio data will be pulled from the output ports of connected unit + * generators. The final unit in a tree of units needs to be start()ed. + * + * @param pullDataEnabled + */ + public void setPullDataEnabled(boolean pullDataEnabled) { + this.pullDataEnabled = pullDataEnabled; + } + + private void setupAudioBuffers(int numInputChannels, int numOutputChannels) { + inputBuffer = new InterleavingBuffer(FRAMES_PER_BUFFER, Synthesizer.FRAMES_PER_BLOCK, + numInputChannels); + outputBuffer = new InterleavingBuffer(FRAMES_PER_BUFFER, Synthesizer.FRAMES_PER_BLOCK, + numOutputChannels); + } + + public void terminate() { + } + + class InterleavingBuffer { + private final double[] interleavedBuffer; + ChannelBlockBuffer[] blockBuffers; + + InterleavingBuffer(int framesPerBuffer, int framesPerBlock, int samplesPerFrame) { + interleavedBuffer = new double[framesPerBuffer * samplesPerFrame]; + // Allocate buffers for each channel of synthesis output. + blockBuffers = new ChannelBlockBuffer[samplesPerFrame]; + for (int i = 0; i < blockBuffers.length; i++) { + blockBuffers[i] = new ChannelBlockBuffer(framesPerBlock); + } + } + + int deinterleave(int inIndex) { + for (int jf = 0; jf < Synthesizer.FRAMES_PER_BLOCK; jf++) { + for (int iob = 0; iob < blockBuffers.length; iob++) { + ChannelBlockBuffer buffer = blockBuffers[iob]; + buffer.values[jf] = interleavedBuffer[inIndex++]; + } + } + return inIndex; + } + + int interleave(int outIndex) { + for (int jf = 0; jf < Synthesizer.FRAMES_PER_BLOCK; jf++) { + for (int iob = 0; iob < blockBuffers.length; iob++) { + ChannelBlockBuffer buffer = blockBuffers[iob]; + interleavedBuffer[outIndex++] = buffer.values[jf]; + } + } + return outIndex; + } + + public double[] getChannelBuffer(int i) { + return blockBuffers[i].values; + } + + public void clear() { + for (int i = 0; i < blockBuffers.length; i++) { + blockBuffers[i].clear(); + } + } + } + + static class ChannelBlockBuffer { + private final double[] values; + + ChannelBlockBuffer(int framesPerBlock) { + values = new double[framesPerBlock]; + } + + void clear() { + for (int i = 0; i < values.length; i++) { + values[i] = 0.0f; + } + } + } + + @Override + public void start() { + // TODO Use constants. + start(DEFAULT_FRAME_RATE, -1, 0, -1, 2); + } + + @Override + public void start(int frameRate) { + // TODO Use constants. + start(frameRate, -1, 0, -1, 2); + } + + @Override + public synchronized void start(int frameRate, int inputDeviceID, int numInputChannels, + int outputDeviceID, int numOutputChannels) { + if (started) { + LOGGER.info("JSyn already started."); + return; + } + + this.frameRate = frameRate; + this.framePeriod = 1.0 / frameRate; + + setupAudioBuffers(numInputChannels, numOutputChannels); + + LOGGER.info("Pure Java JSyn from www.softsynth.com, rate = " + frameRate + ", " + + (useRealTime ? "RT" : "NON-RealTime") + ", " + JSyn.VERSION_TEXT); + + inverseNyquist = 2.0 / frameRate; + + if (useRealTime) { + engineThread = new EngineThread(inputDeviceID, numInputChannels, + outputDeviceID, numOutputChannels); + LOGGER.debug("Synth thread old priority = " + engineThread.getPriority()); + int engineThreadPriority = engineThread.getPriority() + 2 > Thread.MAX_PRIORITY ? + Thread.MAX_PRIORITY : engineThread.getPriority() + 2; + engineThread.setPriority(engineThreadPriority); + LOGGER.debug("Synth thread new priority = " + engineThread.getPriority()); + engineThread.start(); + } + + started = true; + } + + @Override + public boolean isRunning() { + Thread thread = engineThread; + return (thread != null) && thread.isAlive(); + } + + @Override + public synchronized void stop() { + if (!started) { + LOGGER.info("JSyn already stopped."); + return; + } + + if (useRealTime) { + // Stop audio synthesis and all units. + if (engineThread != null) { + try { + // Interrupt now, otherwise audio thread will wait for audio I/O. + engineThread.requestStop(); + engineThread.join(MAX_THREAD_STOP_TIME); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + synchronized (runningUnitList) { + runningUnitList.clear(); + } + started = false; + } + + private class EngineThread extends Thread + { + private AudioDeviceOutputStream audioOutputStream; + private AudioDeviceInputStream audioInputStream; + private volatile boolean go = true; + + EngineThread(int inputDeviceID, int numInputChannels, + int outputDeviceID, int numOutputChannels) { + if (numInputChannels > 0) { + audioInputStream = audioDeviceManager.createInputStream(inputDeviceID, frameRate, + numInputChannels); + } + if (numOutputChannels > 0) { + audioOutputStream = audioDeviceManager.createOutputStream(outputDeviceID, + frameRate, numOutputChannels); + } + } + + public void requestStop() { + go = false; + interrupt(); + } + + @Override + public void run() { + LOGGER.debug("JSyn synthesis thread starting."); + try { + if (audioInputStream != null) { + LOGGER.debug("JSyn synthesis thread trying to start audio INPUT!"); + audioInputStream.start(); + mInputLatency = audioInputStream.getLatency(); + String msg = String.format("Input Latency in = %5.1f msec", + 1000 * mInputLatency); + LOGGER.debug(msg); + } + if (audioOutputStream != null) { + LOGGER.debug("JSyn synthesis thread trying to start audio OUTPUT!"); + audioOutputStream.start(); + mOutputLatency = audioOutputStream.getLatency(); + String msg = String.format("Output Latency = %5.1f msec", + 1000 * mOutputLatency); + LOGGER.debug(msg); + // Buy some time while we fill the buffer. + audioOutputStream.write(outputBuffer.interleavedBuffer); + } + loadAnalyzer = new LoadAnalyzer(); + while (go) { + boolean throttled = false; + if (audioInputStream != null) { + // This call will block when the input is empty. + audioInputStream.read(inputBuffer.interleavedBuffer); + throttled = true; + } + + loadAnalyzer.start(); + runAudioTasks(); + generateNextBuffer(); + loadAnalyzer.stop(); + + if (audioOutputStream != null) { + // This call will block when the output is full. + audioOutputStream.write(outputBuffer.interleavedBuffer); + throttled = true; + } + if (!throttled && isRealTime()) { + Thread.sleep(2); // avoid spinning and eating up CPU + } + } + + } catch (Throwable e) { + e.printStackTrace(); + go = false; + + } finally { + LOGGER.info("JSyn synthesis thread in finally code."); + // Stop audio system. + if (audioInputStream != null) { + audioInputStream.stop(); + } + if (audioOutputStream != null) { + audioOutputStream.stop(); + } + } + LOGGER.debug("JSyn synthesis thread exiting."); + } + } + + private void runAudioTasks() { + for (Runnable task : audioTasks) { + task.run(); + } + } + + // TODO We need to implement a sharedSleeper like we use in JSyn1. + public void generateNextBuffer() { + int outIndex = 0; + int inIndex = 0; + for (int i = 0; i < BLOCKS_PER_BUFFER; i++) { + if (inputBuffer != null) { + inIndex = inputBuffer.deinterleave(inIndex); + } + + TimeStamp timeStamp = createTimeStamp(); + // Try putting this up here so incoming time-stamped events will get + // scheduled later. + processScheduledCommands(timeStamp); + clearBlockBuffers(); + synthesizeBuffer(); + + if (outputBuffer != null) { + outIndex = outputBuffer.interleave(outIndex); + } + frameCount += Synthesizer.FRAMES_PER_BLOCK; + } + } + + @Override + public double getCurrentTime() { + return frameCount * framePeriod; + } + + @Override + public TimeStamp createTimeStamp() { + return new TimeStamp(getCurrentTime()); + } + + private void processScheduledCommands(TimeStamp timeStamp) { + List timeList = commandQueue.removeNextList(timeStamp); + + while (timeList != null) { + while (!timeList.isEmpty()) { + ScheduledCommand command = timeList.remove(0); + LOGGER.debug("processing " + command + ", at time " + timeStamp.getTime()); + command.run(); + } + // Get next list of commands at the given time. + timeList = commandQueue.removeNextList(timeStamp); + } + } + + @Override + public void scheduleCommand(TimeStamp timeStamp, ScheduledCommand command) { + if ((Thread.currentThread() == engineThread) && (timeStamp.getTime() <= getCurrentTime())) { + command.run(); + } else { + LOGGER.debug("scheduling " + command + ", at time " + timeStamp.getTime()); + commandQueue.add(timeStamp, command); + } + } + + @Override + public void scheduleCommand(double time, ScheduledCommand command) { + TimeStamp timeStamp = new TimeStamp(time); + scheduleCommand(timeStamp, command); + } + + @Override + public void queueCommand(ScheduledCommand command) { + TimeStamp timeStamp = createTimeStamp(); + scheduleCommand(timeStamp, command); + } + + @Override + public void clearCommandQueue() { + commandQueue.clear(); + } + + private void clearBlockBuffers() { + outputBuffer.clear(); + } + + private void synthesizeBuffer() { + synchronized (runningUnitList) { + ListIterator iterator = runningUnitList.listIterator(); + while (iterator.hasNext()) { + UnitGenerator unit = iterator.next(); + if (pullDataEnabled) { + unit.pullData(getFrameCount(), 0, Synthesizer.FRAMES_PER_BLOCK); + } else { + unit.generate(0, Synthesizer.FRAMES_PER_BLOCK); + } + } + // Remove any units that got auto stopped. + for (UnitGenerator ugen : stoppingUnitList) { + runningUnitList.remove(ugen); + ugen.flattenOutputs(); + } + } + stoppingUnitList.clear(); + } + + public double[] getInputBuffer(int i) { + try { + return inputBuffer.getChannelBuffer(i); + } catch (ArrayIndexOutOfBoundsException e) { + throw new RuntimeException("Audio Input not configured in start() method."); + } + } + + public double[] getOutputBuffer(int i) { + try { + return outputBuffer.getChannelBuffer(i); + } catch (ArrayIndexOutOfBoundsException e) { + throw new RuntimeException("Audio Output not configured in start() method."); + } + } + + private void internalStopUnit(UnitGenerator unit) { + synchronized (runningUnitList) { + runningUnitList.remove(unit); + } + unit.flattenOutputs(); + } + + public void autoStopUnit(UnitGenerator unitGenerator) { + synchronized (stoppingUnitList) { + stoppingUnitList.add(unitGenerator); + } + } + + @Override + public void startUnit(UnitGenerator unit, double time) { + startUnit(unit, new TimeStamp(time)); + } + + @Override + public void stopUnit(UnitGenerator unit, double time) { + stopUnit(unit, new TimeStamp(time)); + } + + @Override + public void startUnit(final UnitGenerator unit, TimeStamp timeStamp) { + // Don't start if it is a component in a circuit because it will be + // executed by the circuit. + if (unit.getCircuit() == null) { + scheduleCommand(timeStamp, new ScheduledCommand() { + @Override + public void run() { + internalStartUnit(unit); + } + }); + } + } + + @Override + public void stopUnit(final UnitGenerator unit, TimeStamp timeStamp) { + scheduleCommand(timeStamp, new ScheduledCommand() { + @Override + public void run() { + internalStopUnit(unit); + } + }); + } + + @Override + public void startUnit(UnitGenerator unit) { + startUnit(unit, createTimeStamp()); + } + + @Override + public void stopUnit(UnitGenerator unit) { + stopUnit(unit, createTimeStamp()); + } + + private void internalStartUnit(UnitGenerator unit) { + // LOGGER.info( "internalStartUnit " + unit + " with circuit " + + // unit.getCircuit() ); + if (unit.getCircuit() == null) { + synchronized (runningUnitList) { + if (!runningUnitList.contains(unit)) { + runningUnitList.add(unit); + } + } + } + // else + // { + // LOGGER.info( + // "internalStartUnit detected race condition !!!! from old JSyn" + unit + // + " with circuit " + unit.getCircuit() ); + // } + } + + public double getInverseNyquist() { + return inverseNyquist; + } + + public double convertTimeToExponentialScaler(double duration) { + // Calculate scaler so that scaler^frames = target/source + double numFrames = duration * getFrameRate(); + return Math.pow(DB90, (1.0 / numFrames)); + } + + @Override + public long getFrameCount() { + return frameCount; + } + + /** + * @return the frameRate + */ + @Override + public int getFrameRate() { + return frameRate; + } + + /** + * @return the inverse of the frameRate for efficiency + */ + @Override + public double getFramePeriod() { + return framePeriod; + } + + /** Convert a short value to a double in the range -1.0 to almost 1.0. */ + public static double convertShortToDouble(short sdata) { + return (sdata * (1.0 / Short.MAX_VALUE)); + } + + /** + * Convert a double value in the range -1.0 to almost 1.0 to a short. Double value is clipped + * before converting. + */ + public static short convertDoubleToShort(double d) { + final double maxValue = ((double) (Short.MAX_VALUE - 1)) / Short.MAX_VALUE; + if (d > maxValue) { + d = maxValue; + } else if (d < -1.0) { + d = -1.0; + } + return (short) (d * Short.MAX_VALUE); + } + + @Override + public void addAudioTask(Runnable blockTask) { + audioTasks.add(blockTask); + } + + @Override + public void removeAudioTask(Runnable blockTask) { + audioTasks.remove(blockTask); + } + + @Override + public double getUsage() { + // use temp so we don't have to synchronize + LoadAnalyzer temp = loadAnalyzer; + if (temp != null) { + return temp.getAverageLoad(); + } else { + return 0.0; + } + } + + @Override + public AudioDeviceManager getAudioDeviceManager() { + return audioDeviceManager; + } + + @Override + public void setRealTime(boolean realTime) { + useRealTime = realTime; + } + + @Override + public boolean isRealTime() { + return useRealTime; + } + + public double getOutputLatency() { + return mOutputLatency; + } + + public double getInputLatency() { + return mInputLatency; + } + + @Override + public void add(UnitGenerator ugen) { + ugen.setSynthesisEngine(this); + allUnitList.add(ugen); + } + + @Override + public void remove(UnitGenerator ugen) { + allUnitList.remove(ugen); + } + + @Override + public void sleepUntil(double time) throws InterruptedException { + double timeToSleep = time - getCurrentTime(); + while (timeToSleep > 0.0) { + if (useRealTime) { + long msecToSleep = (long) (1000 * timeToSleep); + if (msecToSleep <= 0) { + msecToSleep = 1; + } + Thread.sleep(msecToSleep); + } else { + + generateNextBuffer(); + } + timeToSleep = time - getCurrentTime(); + } + } + + @Override + public void sleepFor(double duration) throws InterruptedException { + sleepUntil(getCurrentTime() + duration); + } + + public void printConnections() { + if (pullDataEnabled) { + ListIterator iterator = runningUnitList.listIterator(); + while (iterator.hasNext()) { + UnitGenerator unit = iterator.next(); + unit.printConnections(); + } + } + + } + +} diff --git a/src/main/java/com/jsyn/exceptions/ChannelMismatchException.java b/src/main/java/com/jsyn/exceptions/ChannelMismatchException.java new file mode 100644 index 0000000..a1554cd --- /dev/null +++ b/src/main/java/com/jsyn/exceptions/ChannelMismatchException.java @@ -0,0 +1,35 @@ +/* + * Copyright 2010 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.exceptions; + +/** + * This will get thrown if, for example, stereo data is queued to a mono player. + * + * @author Phil Burk (C) 2010 Mobileer Inc + */ +public class ChannelMismatchException extends RuntimeException { + + public ChannelMismatchException(String message) { + super(message); + } + + /** + * + */ + private static final long serialVersionUID = -5345224363387498119L; + +} diff --git a/src/main/java/com/jsyn/instruments/DrumWoodFM.java b/src/main/java/com/jsyn/instruments/DrumWoodFM.java new file mode 100644 index 0000000..ba6cd1b --- /dev/null +++ b/src/main/java/com/jsyn/instruments/DrumWoodFM.java @@ -0,0 +1,159 @@ +/* + * Copyright 2011 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.instruments; + +import com.jsyn.ports.UnitInputPort; +import com.jsyn.ports.UnitOutputPort; +import com.jsyn.unitgen.Add; +import com.jsyn.unitgen.Circuit; +import com.jsyn.unitgen.EnvelopeAttackDecay; +import com.jsyn.unitgen.Multiply; +import com.jsyn.unitgen.PassThrough; +import com.jsyn.unitgen.SineOscillator; +import com.jsyn.unitgen.SineOscillatorPhaseModulated; +import com.jsyn.unitgen.UnitVoice; +import com.jsyn.util.VoiceDescription; +import com.softsynth.shared.time.TimeStamp; + +/** + * Drum instruments using 2 Operator FM. + * + * @author Phil Burk (C) 2011 Mobileer Inc + */ +public class DrumWoodFM extends Circuit implements UnitVoice { + private static final int NUM_PRESETS = 3; + // Declare units and ports. + EnvelopeAttackDecay ampEnv; + SineOscillatorPhaseModulated carrierOsc; + EnvelopeAttackDecay modEnv; + SineOscillator modOsc; + PassThrough freqDistributor; + Add modSummer; + Multiply frequencyMultiplier; + + public UnitInputPort mcratio; + public UnitInputPort index; + public UnitInputPort modRange; + public UnitInputPort frequency; + + public DrumWoodFM() { + // Create unit generators. + add(carrierOsc = new SineOscillatorPhaseModulated()); + add(freqDistributor = new PassThrough()); + add(modSummer = new Add()); + add(ampEnv = new EnvelopeAttackDecay()); + add(modEnv = new EnvelopeAttackDecay()); + add(modOsc = new SineOscillator()); + add(frequencyMultiplier = new Multiply()); + + addPort(mcratio = frequencyMultiplier.inputB, "MCRatio"); + addPort(index = modSummer.inputA, "Index"); + addPort(modRange = modEnv.amplitude, "ModRange"); + addPort(frequency = freqDistributor.input, "Frequency"); + + ampEnv.export(this, "Amp"); + modEnv.export(this, "Mod"); + + freqDistributor.output.connect(carrierOsc.frequency); + freqDistributor.output.connect(frequencyMultiplier.inputA); + + carrierOsc.output.connect(ampEnv.amplitude); + modEnv.output.connect(modSummer.inputB); + modSummer.output.connect(modOsc.amplitude); + modOsc.output.connect(carrierOsc.modulation); + frequencyMultiplier.output.connect(modOsc.frequency); + + // Make the circuit turn off when the envelope finishes to reduce CPU load. + ampEnv.setupAutoDisable(this); + + usePreset(0); + } + + @Override + public void noteOff(TimeStamp timeStamp) { + } + + @Override + public void noteOn(double freq, double ampl, TimeStamp timeStamp) { + carrierOsc.amplitude.set(ampl, timeStamp); + ampEnv.input.trigger(timeStamp); + modEnv.input.trigger(timeStamp); + } + + @Override + public UnitOutputPort getOutput() { + return ampEnv.output; + } + + @Override + public void usePreset(int presetIndex) { + mcratio.setup(0.001, 0.6875, 20.0); + ampEnv.attack.setup(0.001, 0.005, 8.0); + modEnv.attack.setup(0.001, 0.005, 8.0); + + int n = presetIndex % NUM_PRESETS; + switch (n) { + case 0: + ampEnv.decay.setup(0.001, 0.293, 8.0); + modEnv.decay.setup(0.001, 0.07, 8.0); + frequency.setup(0.0, 349.0, 3000.0); + index.setup(0.001, 0.05, 10.0); + modRange.setup(0.001, 0.4, 10.0); + break; + case 1: + default: + ampEnv.decay.setup(0.001, 0.12, 8.0); + modEnv.decay.setup(0.001, 0.06, 8.0); + frequency.setup(0.0, 1400.0, 3000.0); + index.setup(0.001, 0.16, 10.0); + modRange.setup(0.001, 0.17, 10.0); + break; + } + } + + static class MyVoiceDescription extends VoiceDescription { + static String[] presetNames = { + "WoodBlockFM", "ClaveFM" + }; + static String[] tags = { + "electronic", "drum" + }; + + public MyVoiceDescription() { + super("DrumWoodFM", presetNames); + } + + @Override + public UnitVoice createUnitVoice() { + return new DrumWoodFM(); + } + + @Override + public String[] getTags(int presetIndex) { + return tags; + } + + @Override + public String getVoiceClassName() { + return DrumWoodFM.class.getName(); + } + } + + public static VoiceDescription getVoiceDescription() { + return new MyVoiceDescription(); + } +} diff --git a/src/main/java/com/jsyn/instruments/DualOscillatorSynthVoice.java b/src/main/java/com/jsyn/instruments/DualOscillatorSynthVoice.java new file mode 100644 index 0000000..c81041f --- /dev/null +++ b/src/main/java/com/jsyn/instruments/DualOscillatorSynthVoice.java @@ -0,0 +1,301 @@ +/* + * Copyright 2010 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.instruments; + +import com.jsyn.ports.UnitInputPort; +import com.jsyn.ports.UnitOutputPort; +import com.jsyn.unitgen.Add; +import com.jsyn.unitgen.Circuit; +import com.jsyn.unitgen.EnvelopeDAHDSR; +import com.jsyn.unitgen.FilterFourPoles; +import com.jsyn.unitgen.MorphingOscillatorBL; +import com.jsyn.unitgen.Multiply; +import com.jsyn.unitgen.UnitVoice; +import com.jsyn.util.VoiceDescription; +import com.softsynth.math.AudioMath; +import com.softsynth.shared.time.TimeStamp; + +/** + * Synthesizer voice with two morphing oscillators and a four-pole resonant filter. + * Modulate the amplitude and filter using DAHDSR envelopes. + */ +public class DualOscillatorSynthVoice extends Circuit implements UnitVoice { + private Multiply frequencyMultiplier; + private Multiply amplitudeMultiplier; + private Multiply detuneScaler1; + private Multiply detuneScaler2; + private Multiply amplitudeBoost; + private MorphingOscillatorBL osc1; + private MorphingOscillatorBL osc2; + private FilterFourPoles filter; + private EnvelopeDAHDSR ampEnv; + private EnvelopeDAHDSR filterEnv; + private Add cutoffAdder; + + private static MyVoiceDescription voiceDescription; + + public UnitInputPort amplitude; + public UnitInputPort frequency; + /** + * This scales the frequency value. You can use this to modulate a group of instruments using a + * shared LFO and they will stay in tune. Set to 1.0 for no modulation. + */ + public UnitInputPort frequencyScaler; + public UnitInputPort oscShape1; + public UnitInputPort oscShape2; +// public UnitInputPort oscDetune1; +// public UnitInputPort oscDetune2; + public UnitInputPort cutoff; + public UnitInputPort filterEnvDepth; + public UnitInputPort Q; + + public DualOscillatorSynthVoice() { + add(frequencyMultiplier = new Multiply()); + add(amplitudeMultiplier = new Multiply()); + add(amplitudeBoost = new Multiply()); + add(detuneScaler1 = new Multiply()); + add(detuneScaler2 = new Multiply()); + // Add tone generators. + add(osc1 = new MorphingOscillatorBL()); + add(osc2 = new MorphingOscillatorBL()); + + // Use an envelope to control the amplitude. + add(ampEnv = new EnvelopeDAHDSR()); + + // Use an envelope to control the filter cutoff. + add(filterEnv = new EnvelopeDAHDSR()); + add(filter = new FilterFourPoles()); + add(cutoffAdder = new Add()); + + filterEnv.output.connect(cutoffAdder.inputA); + cutoffAdder.output.connect(filter.frequency); + frequencyMultiplier.output.connect(detuneScaler1.inputA); + frequencyMultiplier.output.connect(detuneScaler2.inputA); + detuneScaler1.output.connect(osc1.frequency); + detuneScaler2.output.connect(osc2.frequency); + osc1.output.connect(amplitudeMultiplier.inputA); // mix oscillators + osc2.output.connect(amplitudeMultiplier.inputA); + amplitudeMultiplier.output.connect(filter.input); + filter.output.connect(amplitudeBoost.inputA); + amplitudeBoost.output.connect(ampEnv.amplitude); + + addPort(amplitude = amplitudeMultiplier.inputB, PORT_NAME_AMPLITUDE); + addPort(frequency = frequencyMultiplier.inputA, PORT_NAME_FREQUENCY); + addPort(oscShape1 = osc1.shape, "OscShape1"); + addPort(oscShape2 = osc2.shape, "OscShape2"); +// addPort(oscDetune1 = osc1.shape, "OscDetune1"); +// addPort(oscDetune2 = osc2.shape, "OscDetune2"); + addPort(cutoff = cutoffAdder.inputB, PORT_NAME_CUTOFF); + addPortAlias(cutoff, PORT_NAME_TIMBRE); + addPort(Q = filter.Q); + addPort(frequencyScaler = frequencyMultiplier.inputB, PORT_NAME_FREQUENCY_SCALER); + addPort(filterEnvDepth = filterEnv.amplitude, "FilterEnvDepth"); + + filterEnv.export(this, "Filter"); + ampEnv.export(this, "Amp"); + + frequency.setup(osc1.frequency); + frequencyScaler.setup(0.2, 1.0, 4.0); + cutoff.setup(filter.frequency); + // Allow negative filter sweeps + filterEnvDepth.setup(-4000.0, 2000.0, 4000.0); + + // set amplitudes slightly different so that they never entirely cancel + osc1.amplitude.set(0.5); + osc2.amplitude.set(0.4); + // Make the circuit turn off when the envelope finishes to reduce CPU load. + ampEnv.setupAutoDisable(this); + // Add named port for mapping pressure. + amplitudeBoost.inputB.setup(1.0, 1.0, 4.0); + addPortAlias(amplitudeBoost.inputB, PORT_NAME_PRESSURE); + + usePreset(0); + } + + /** + * The first oscillator will be tuned UP by semitoneOffset/2. + * The second oscillator will be tuned DOWN by semitoneOffset/2. + * @param semitoneOffset + */ + private void setDetunePitch(double semitoneOffset) { + double halfOffset = semitoneOffset * 0.5; + setDetunePitch1(halfOffset); + setDetunePitch2(-halfOffset); + } + + /** + * Set the detuning for osc1 in semitones. + * @param semitoneOffset + */ + private void setDetunePitch1(double semitoneOffset) { + double scale = AudioMath.semitonesToFrequencyScaler(semitoneOffset); + detuneScaler1.inputB.set(scale); + } + + /** + * Set the detuning for osc2 in semitones. + * @param semitoneOffset + */ + private void setDetunePitch2(double semitoneOffset) { + double scale = AudioMath.semitonesToFrequencyScaler(semitoneOffset); + detuneScaler2.inputB.set(scale); + } + + @Override + public void noteOff(TimeStamp timeStamp) { + ampEnv.input.off(timeStamp); + filterEnv.input.off(timeStamp); + } + + @Override + public void noteOn(double freq, double ampl, TimeStamp timeStamp) { + frequency.set(freq, timeStamp); + amplitude.set(ampl, timeStamp); + ampEnv.input.on(timeStamp); + filterEnv.input.on(timeStamp); + } + + @Override + public UnitOutputPort getOutput() { + return ampEnv.output; + } + + // Reset to basic voice. + public void reset() { + osc1.shape.set(0.0); + osc2.shape.set(0.0); + ampEnv.attack.set(0.005); + ampEnv.decay.set(0.2); + ampEnv.sustain.set(0.5); + ampEnv.release.set(1.0); + filterEnv.attack.set(0.01); + filterEnv.decay.set(0.6); + filterEnv.sustain.set(0.4); + filterEnv.release.set(1.0); + cutoff.set(500.0); + filterEnvDepth.set(3000.0); + filter.reset(); + filter.Q.set(3.9); + setDetunePitch(0.02); + } + + @Override + public void usePreset(int presetIndex) { + reset(); // start from known configuration + int n = presetIndex % presetNames.length; + switch (n) { + case 0: + break; + case 1: + ampEnv.attack.set(0.1); + ampEnv.decay.set(0.9); + ampEnv.sustain.set(0.1); + ampEnv.release.set(0.1); + cutoff.set(500.0); + filterEnvDepth.set(500.0); + filter.Q.set(3.0); + break; + case 2: + ampEnv.attack.set(0.1); + ampEnv.decay.set(0.3); + ampEnv.release.set(0.5); + cutoff.set(2000.0); + filterEnvDepth.set(500.0); + filter.Q.set(2.0); + break; + case 3: + osc1.shape.set(-0.9); + osc2.shape.set(-0.8); + ampEnv.attack.set(0.3); + ampEnv.decay.set(0.8); + ampEnv.release.set(0.2); + filterEnv.sustain.set(0.7); + cutoff.set(500.0); + filterEnvDepth.set(500.0); + filter.Q.set(3.0); + break; + case 4: + osc1.shape.set(1.0); + osc2.shape.set(0.0); + break; + case 5: + osc1.shape.set(1.0); + setDetunePitch1(0.0); + osc2.shape.set(0.9); + setDetunePitch1(7.0); + break; + case 6: + osc1.shape.set(0.6); + osc2.shape.set(-0.2); + setDetunePitch1(0.01); + ampEnv.attack.set(0.005); + ampEnv.decay.set(0.09); + ampEnv.sustain.set(0.0); + ampEnv.release.set(1.0); + filterEnv.attack.set(0.005); + filterEnv.decay.set(0.1); + filterEnv.sustain.set(0.4); + filterEnv.release.set(1.0); + cutoff.set(2000.0); + filterEnvDepth.set(5000.0); + filter.Q.set(7.02); + break; + default: + break; + } + } + + private static final String[] presetNames = { + "FastSaw", "SlowSaw", "BrightSaw", + "SoftSine", "SquareSaw", "SquareFifth", + "Blip" + }; + + static class MyVoiceDescription extends VoiceDescription { + String[] tags = { + "electronic", "filter", "analog", "subtractive" + }; + + public MyVoiceDescription() { + super(DualOscillatorSynthVoice.class.getName(), presetNames); + } + + @Override + public UnitVoice createUnitVoice() { + return new DualOscillatorSynthVoice(); + } + + @Override + public String[] getTags(int presetIndex) { + return tags; + } + + @Override + public String getVoiceClassName() { + return DualOscillatorSynthVoice.class.getName(); + } + } + + public static VoiceDescription getVoiceDescription() { + if (voiceDescription == null) { + voiceDescription = new MyVoiceDescription(); + } + return voiceDescription; + } + + +} diff --git a/src/main/java/com/jsyn/instruments/JSynInstrumentLibrary.java b/src/main/java/com/jsyn/instruments/JSynInstrumentLibrary.java new file mode 100644 index 0000000..9f111c3 --- /dev/null +++ b/src/main/java/com/jsyn/instruments/JSynInstrumentLibrary.java @@ -0,0 +1,48 @@ +/* + * Copyright 2011 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.instruments; + +import com.jsyn.swing.InstrumentBrowser; +import com.jsyn.util.InstrumentLibrary; +import com.jsyn.util.VoiceDescription; + +/** + * Stock instruments provided with the JSyn distribution. + * + * @author Phil Burk (C) 2011 Mobileer Inc + * @see InstrumentBrowser + */ + +public class JSynInstrumentLibrary implements InstrumentLibrary { + static VoiceDescription[] descriptions = { + WaveShapingVoice.getVoiceDescription(), + SubtractiveSynthVoice.getVoiceDescription(), + DualOscillatorSynthVoice.getVoiceDescription(), + NoiseHit.getVoiceDescription(), + DrumWoodFM.getVoiceDescription() + }; + + @Override + public VoiceDescription[] getVoiceDescriptions() { + return descriptions; + } + + @Override + public String getName() { + return "JSynInstruments"; + } +} diff --git a/src/main/java/com/jsyn/instruments/NoiseHit.java b/src/main/java/com/jsyn/instruments/NoiseHit.java new file mode 100644 index 0000000..b8714fc --- /dev/null +++ b/src/main/java/com/jsyn/instruments/NoiseHit.java @@ -0,0 +1,114 @@ +/* + * 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.instruments; + +import com.jsyn.ports.UnitOutputPort; +import com.jsyn.unitgen.Circuit; +import com.jsyn.unitgen.EnvelopeAttackDecay; +import com.jsyn.unitgen.PinkNoise; +import com.jsyn.unitgen.UnitVoice; +import com.jsyn.util.VoiceDescription; +import com.softsynth.shared.time.TimeStamp; + +/** + * Cheap synthetic cymbal sound. + */ +public class NoiseHit extends Circuit implements UnitVoice { + EnvelopeAttackDecay ampEnv; + PinkNoise noise; + private static final int NUM_PRESETS = 3; + + public NoiseHit() { + // Create unit generators. + add(noise = new PinkNoise()); + add(ampEnv = new EnvelopeAttackDecay()); + noise.output.connect(ampEnv.amplitude); + + ampEnv.export(this, "Amp"); + + // Make the circuit turn off when the envelope finishes to reduce CPU load. + ampEnv.setupAutoDisable(this); + + usePreset(0); + } + + @Override + public void noteOff(TimeStamp timeStamp) { + } + + @Override + public void noteOn(double freq, double ampl, TimeStamp timeStamp) { + noise.amplitude.set(ampl, timeStamp); + ampEnv.input.trigger(); + } + + @Override + public UnitOutputPort getOutput() { + return ampEnv.output; + } + + @Override + public void usePreset(int presetIndex) { + int n = presetIndex % NUM_PRESETS; + switch (n) { + case 0: + ampEnv.attack.set(0.001); + ampEnv.decay.set(0.1); + break; + case 1: + ampEnv.attack.set(0.03); + ampEnv.decay.set(1.4); + break; + default: + ampEnv.attack.set(0.9); + ampEnv.decay.set(0.3); + break; + } + } + + static class MyVoiceDescription extends VoiceDescription { + static String[] presetNames = { + "ShortNoiseHit", "LongNoiseHit", "SlowNoiseHit" + }; + static String[] tags = { + "electronic", "noise" + }; + + public MyVoiceDescription() { + super("NoiseHit", presetNames); + } + + @Override + public UnitVoice createUnitVoice() { + return new NoiseHit(); + } + + @Override + public String[] getTags(int presetIndex) { + return tags; + } + + @Override + public String getVoiceClassName() { + return NoiseHit.class.getName(); + } + } + + public static VoiceDescription getVoiceDescription() { + return new MyVoiceDescription(); + } +} diff --git a/src/main/java/com/jsyn/instruments/SubtractiveSynthVoice.java b/src/main/java/com/jsyn/instruments/SubtractiveSynthVoice.java new file mode 100644 index 0000000..5cfc4b9 --- /dev/null +++ b/src/main/java/com/jsyn/instruments/SubtractiveSynthVoice.java @@ -0,0 +1,182 @@ +/* + * Copyright 2010 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.instruments; + +import com.jsyn.ports.UnitInputPort; +import com.jsyn.ports.UnitOutputPort; +import com.jsyn.unitgen.Add; +import com.jsyn.unitgen.Circuit; +import com.jsyn.unitgen.EnvelopeDAHDSR; +import com.jsyn.unitgen.FilterLowPass; +import com.jsyn.unitgen.Multiply; +import com.jsyn.unitgen.SawtoothOscillatorBL; +import com.jsyn.unitgen.UnitOscillator; +import com.jsyn.unitgen.UnitVoice; +import com.jsyn.util.VoiceDescription; +import com.softsynth.shared.time.TimeStamp; + +/** + * Typical synthesizer voice with one oscillator and a biquad resonant filter. Modulate the amplitude and + * filter using DAHDSR envelopes. + * + * @author Phil Burk (C) 2010 Mobileer Inc + */ +public class SubtractiveSynthVoice extends Circuit implements UnitVoice { + private UnitOscillator osc; + private FilterLowPass filter; + private EnvelopeDAHDSR ampEnv; + private EnvelopeDAHDSR filterEnv; + private Add cutoffAdder; + private Multiply frequencyScaler; + + public UnitInputPort amplitude; + public UnitInputPort frequency; + /** + * This scales the frequency value. You can use this to modulate a group of instruments using a + * shared LFO and they will stay in tune. + */ + public UnitInputPort pitchModulation; + public UnitInputPort cutoff; + public UnitInputPort cutoffRange; + public UnitInputPort Q; + + public SubtractiveSynthVoice() { + add(frequencyScaler = new Multiply()); + // Add a tone generator. + add(osc = new SawtoothOscillatorBL()); + + // Use an envelope to control the amplitude. + add(ampEnv = new EnvelopeDAHDSR()); + + // Use an envelope to control the filter cutoff. + add(filterEnv = new EnvelopeDAHDSR()); + add(filter = new FilterLowPass()); + add(cutoffAdder = new Add()); + + filterEnv.output.connect(cutoffAdder.inputA); + cutoffAdder.output.connect(filter.frequency); + frequencyScaler.output.connect(osc.frequency); + osc.output.connect(filter.input); + filter.output.connect(ampEnv.amplitude); + + addPort(amplitude = osc.amplitude, "Amplitude"); + addPort(frequency = frequencyScaler.inputA, "Frequency"); + addPort(pitchModulation = frequencyScaler.inputB, "PitchMod"); + addPort(cutoff = cutoffAdder.inputB, "Cutoff"); + addPort(cutoffRange = filterEnv.amplitude, "CutoffRange"); + addPort(Q = filter.Q); + + ampEnv.export(this, "Amp"); + filterEnv.export(this, "Filter"); + + frequency.setup(osc.frequency); + pitchModulation.setup(0.2, 1.0, 4.0); + cutoff.setup(filter.frequency); + cutoffRange.setup(filter.frequency); + + // Make the circuit turn off when the envelope finishes to reduce CPU load. + ampEnv.setupAutoDisable(this); + + usePreset(0); + } + + @Override + public void noteOff(TimeStamp timeStamp) { + ampEnv.input.off(timeStamp); + filterEnv.input.off(timeStamp); + } + + @Override + public void noteOn(double freq, double ampl, TimeStamp timeStamp) { + frequency.set(freq, timeStamp); + amplitude.set(ampl, timeStamp); + + ampEnv.input.on(timeStamp); + filterEnv.input.on(timeStamp); + } + + @Override + public UnitOutputPort getOutput() { + return ampEnv.output; + } + + @Override + public void usePreset(int presetIndex) { + int n = presetIndex % presetNames.length; + switch (n) { + case 0: + ampEnv.attack.set(0.01); + ampEnv.decay.set(0.2); + ampEnv.release.set(1.0); + cutoff.set(500.0); + cutoffRange.set(500.0); + filter.Q.set(1.0); + break; + case 1: + ampEnv.attack.set(0.5); + ampEnv.decay.set(0.3); + ampEnv.release.set(0.2); + cutoff.set(500.0); + cutoffRange.set(500.0); + filter.Q.set(3.0); + break; + case 2: + default: + ampEnv.attack.set(0.1); + ampEnv.decay.set(0.3); + ampEnv.release.set(0.5); + cutoff.set(2000.0); + cutoffRange.set(500.0); + filter.Q.set(2.0); + break; + } + } + + static String[] presetNames = { + "FastSaw", "SlowSaw", "BrightSaw" + }; + + static class MyVoiceDescription extends VoiceDescription { + String[] tags = { + "electronic", "filter", "clean" + }; + + public MyVoiceDescription() { + super("SubtractiveSynth", presetNames); + } + + @Override + public UnitVoice createUnitVoice() { + return new SubtractiveSynthVoice(); + } + + @Override + public String[] getTags(int presetIndex) { + return tags; + } + + @Override + public String getVoiceClassName() { + return SubtractiveSynthVoice.class.getName(); + } + } + + public static VoiceDescription getVoiceDescription() { + return new MyVoiceDescription(); + } + +} diff --git a/src/main/java/com/jsyn/instruments/WaveShapingVoice.java b/src/main/java/com/jsyn/instruments/WaveShapingVoice.java new file mode 100644 index 0000000..5044f21 --- /dev/null +++ b/src/main/java/com/jsyn/instruments/WaveShapingVoice.java @@ -0,0 +1,187 @@ +/* + * Copyright 2011 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.instruments; + +import com.jsyn.data.DoubleTable; +import com.jsyn.ports.UnitFunctionPort; +import com.jsyn.ports.UnitInputPort; +import com.jsyn.ports.UnitOutputPort; +import com.jsyn.unitgen.Circuit; +import com.jsyn.unitgen.EnvelopeDAHDSR; +import com.jsyn.unitgen.FunctionEvaluator; +import com.jsyn.unitgen.Multiply; +import com.jsyn.unitgen.SineOscillator; +import com.jsyn.unitgen.UnitOscillator; +import com.jsyn.unitgen.UnitVoice; +import com.jsyn.util.VoiceDescription; +import com.softsynth.math.ChebyshevPolynomial; +import com.softsynth.math.PolynomialTableData; +import com.softsynth.shared.time.TimeStamp; + +/** + * Waveshaping oscillator with envelopes. + * + * @author Phil Burk (C) 2011 Mobileer Inc + */ +public class WaveShapingVoice extends Circuit implements UnitVoice { + private static final long serialVersionUID = -2704222221111608377L; + private static final int NUM_PRESETS = 3; + private UnitOscillator osc; + private FunctionEvaluator waveShaper; + private EnvelopeDAHDSR ampEnv; + private EnvelopeDAHDSR rangeEnv; + private Multiply frequencyScaler; + + public UnitInputPort range; + public UnitInputPort frequency; + public UnitInputPort amplitude; + public UnitFunctionPort function; + public UnitInputPort pitchModulation; + + // default Chebyshev polynomial table to share. + private static DoubleTable chebyshevTable; + private final static int CHEBYSHEV_ORDER = 11; + + static { + // Make table with Chebyshev polynomial to share among voices + PolynomialTableData chebData = new PolynomialTableData( + ChebyshevPolynomial.T(CHEBYSHEV_ORDER), 1024); + chebyshevTable = new DoubleTable(chebData.getData()); + } + + public WaveShapingVoice() { + add(frequencyScaler = new Multiply()); + add(osc = new SineOscillator()); + add(waveShaper = new FunctionEvaluator()); + add(rangeEnv = new EnvelopeDAHDSR()); + add(ampEnv = new EnvelopeDAHDSR()); + + addPort(amplitude = ampEnv.amplitude); + addPort(range = osc.amplitude, "Range"); + addPort(function = waveShaper.function); + addPort(frequency = frequencyScaler.inputA, "Frequency"); + addPort(pitchModulation = frequencyScaler.inputB, "PitchMod"); + + ampEnv.export(this, "Amp"); + rangeEnv.export(this, "Range"); + + function.set(chebyshevTable); + + // Connect units. + osc.output.connect(rangeEnv.amplitude); + rangeEnv.output.connect(waveShaper.input); + ampEnv.output.connect(waveShaper.amplitude); + frequencyScaler.output.connect(osc.frequency); + + // Set reasonable defaults for the ports. + pitchModulation.setup(0.1, 1.0, 10.0); + range.setup(0.1, 0.8, 1.0); + frequency.setup(osc.frequency); + amplitude.setup(0.0, 0.5, 1.0); + + // Make the circuit turn off when the envelope finishes to reduce CPU load. + ampEnv.setupAutoDisable(this); + + usePreset(2); + } + + @Override + public UnitOutputPort getOutput() { + return waveShaper.output; + } + + @Override + public void noteOn(double freq, double amp, TimeStamp timeStamp) { + frequency.set(freq, timeStamp); + amplitude.set(amp, timeStamp); + ampEnv.input.on(timeStamp); + rangeEnv.input.on(timeStamp); + } + + @Override + public void noteOff(TimeStamp timeStamp) { + ampEnv.input.off(timeStamp); + rangeEnv.input.off(timeStamp); + } + + @Override + public void usePreset(int presetIndex) { + int n = presetIndex % NUM_PRESETS; + switch (n) { + case 0: + ampEnv.attack.set(0.01); + ampEnv.decay.set(0.2); + ampEnv.release.set(1.0); + rangeEnv.attack.set(0.01); + rangeEnv.decay.set(0.2); + rangeEnv.sustain.set(0.4); + rangeEnv.release.set(1.0); + break; + case 1: + ampEnv.attack.set(0.5); + ampEnv.decay.set(0.3); + ampEnv.release.set(0.2); + rangeEnv.attack.set(0.03); + rangeEnv.decay.set(0.2); + rangeEnv.sustain.set(0.5); + rangeEnv.release.set(1.0); + break; + default: + ampEnv.attack.set(0.1); + ampEnv.decay.set(0.3); + ampEnv.release.set(0.5); + rangeEnv.attack.set(0.01); + rangeEnv.decay.set(0.2); + rangeEnv.sustain.set(0.9); + rangeEnv.release.set(1.0); + break; + } + } + + static class MyVoiceDescription extends VoiceDescription { + static String[] presetNames = { + "FastChebyshev", "SlowChebyshev", "BrightChebyshev" + }; + static String[] tags = { + "electronic", "waveshaping", "clean" + }; + + public MyVoiceDescription() { + super("Waveshaping", presetNames); + } + + @Override + public UnitVoice createUnitVoice() { + return new WaveShapingVoice(); + } + + @Override + public String[] getTags(int presetIndex) { + return tags; + } + + @Override + public String getVoiceClassName() { + return WaveShapingVoice.class.getName(); + } + } + + public static VoiceDescription getVoiceDescription() { + return new MyVoiceDescription(); + } + +} diff --git a/src/main/java/com/jsyn/io/AudioFifo.java b/src/main/java/com/jsyn/io/AudioFifo.java new file mode 100644 index 0000000..0c563e4 --- /dev/null +++ b/src/main/java/com/jsyn/io/AudioFifo.java @@ -0,0 +1,204 @@ +/* + * Copyright 2010 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.io; + +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +/** + * FIFO that implements AudioInputStream, AudioOutputStream interfaces. This can be used to send + * audio data between different threads. The reads or writes may or may not wait based on flags. + * + * @author Phil Burk (C) 2010 Mobileer Inc + */ +public class AudioFifo implements AudioInputStream, AudioOutputStream { + // These indices run double the FIFO size so that we can tell empty from full. + private volatile int readIndex; + private volatile int writeIndex; + private volatile double[] buffer; + // Used to mask the index into range when accessing the buffer array. + private int accessMask; + // Used to mask the index so it wraps around. + private int sizeMask; + private boolean writeWaitEnabled = true; + private boolean readWaitEnabled = true; + final Lock lock = new ReentrantLock(); + final Condition notFull = lock.newCondition(); + final Condition notEmpty = lock.newCondition(); + + /** + * @param size Number of doubles in the FIFO. Must be a power of 2. Eg. 1024. + */ + public void allocate(int size) { + if (!isPowerOfTwo(size)) { + throw new IllegalArgumentException("Size must be a power of two."); + } + buffer = new double[size]; + accessMask = size - 1; + sizeMask = (size * 2) - 1; + } + + public int size() { + return buffer.length; + } + + public static boolean isPowerOfTwo(int size) { + return ((size & (size - 1)) == 0); + } + + /** How many samples are available for reading without blocking? */ + @Override + public int available() { + return (writeIndex - readIndex) & sizeMask; + } + + @Override + public void close() { + // TODO Maybe we should tell any thread that is waiting that the FIFO is closed. + } + + @Override + public double read() { + double value = Double.NaN; + if (readWaitEnabled) { + lock.lock(); + try { + while (available() < 1) { + try { + notEmpty.await(); + } catch (InterruptedException e) { + return Double.NaN; + } + } + value = readOneInternal(); + } finally { + lock.unlock(); + } + + } else { + if (readIndex != writeIndex) { + value = readOneInternal(); + } + } + + if (writeWaitEnabled) { + lock.lock(); + notFull.signal(); + lock.unlock(); + } + + return value; + } + + private double readOneInternal() { + double value = buffer[readIndex & accessMask]; + readIndex = (readIndex + 1) & sizeMask; + return value; + } + + @Override + public void write(double value) { + if (writeWaitEnabled) { + lock.lock(); + try { + while (available() == buffer.length) + { + try { + notFull.await(); + } catch (InterruptedException e) { + return; // Silently fail + } + } + writeOneInternal(value); + } finally { + lock.unlock(); + } + + } else { + if (available() != buffer.length) { + writeOneInternal(value); + } + } + + if (readWaitEnabled) { + lock.lock(); + notEmpty.signal(); + lock.unlock(); + } + } + + private void writeOneInternal(double value) { + buffer[writeIndex & accessMask] = value; + writeIndex = (writeIndex + 1) & sizeMask; + } + + @Override + public int read(double[] buffer) { + return read(buffer, 0, buffer.length); + } + + @Override + public int read(double[] buffer, int start, int count) { + if (readWaitEnabled) { + for (int i = 0; i < count; i++) { + buffer[i + start] = read(); + } + } else { + if (available() < count) { + count = available(); + } else { + for (int i = 0; i < count; i++) { + buffer[i + start] = read(); + } + } + } + return count; + } + + @Override + public void write(double[] buffer) { + write(buffer, 0, buffer.length); + } + + @Override + public void write(double[] buffer, int start, int count) { + for (int i = 0; i < count; i++) { + write(buffer[i + start]); + } + } + + /** If true then a subsequent write call will wait if there is no room to write. */ + public void setWriteWaitEnabled(boolean enabled) { + writeWaitEnabled = enabled; + + } + + /** If true then a subsequent read call will wait if there is no data to read. */ + public void setReadWaitEnabled(boolean enabled) { + readWaitEnabled = enabled; + + } + + public boolean isWriteWaitEnabled() { + return writeWaitEnabled; + } + + public boolean isReadWaitEnabled() { + return readWaitEnabled; + } +} diff --git a/src/main/java/com/jsyn/io/AudioInputStream.java b/src/main/java/com/jsyn/io/AudioInputStream.java new file mode 100644 index 0000000..f233ff1 --- /dev/null +++ b/src/main/java/com/jsyn/io/AudioInputStream.java @@ -0,0 +1,46 @@ +/* + * 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.io; + +public interface AudioInputStream { + public double read(); + + /** + * Try to fill the entire buffer. + * + * @param buffer + * @return number of samples read + */ + public int read(double[] buffer); + + /** + * Read from the stream. Block until some data is available. + * + * @param buffer + * @param start index of first sample in buffer + * @param count number of samples to read, for example count=8 for 4 stereo frames + * @return number of samples read + */ + public int read(double[] buffer, int start, int count); + + public void close(); + + /** + * @return number of samples currently available to read without blocking + */ + public int available(); +} diff --git a/src/main/java/com/jsyn/io/AudioOutputStream.java b/src/main/java/com/jsyn/io/AudioOutputStream.java new file mode 100644 index 0000000..dada577 --- /dev/null +++ b/src/main/java/com/jsyn/io/AudioOutputStream.java @@ -0,0 +1,29 @@ +/* + * 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.io; + +import java.io.IOException; + +public interface AudioOutputStream { + public void write(double value) throws IOException; + + public void write(double[] buffer) throws IOException; + + public void write(double[] buffer, int start, int count) throws IOException; + + public void close() throws IOException; +} diff --git a/src/main/java/com/jsyn/midi/MessageParser.java b/src/main/java/com/jsyn/midi/MessageParser.java new file mode 100644 index 0000000..d0f5d4d --- /dev/null +++ b/src/main/java/com/jsyn/midi/MessageParser.java @@ -0,0 +1,147 @@ +/* + * Copyright 2010 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.midi; + +/** + * Parse the message and call the appropriate method to handle it. + * + * @author Phil Burk (C) 2010 Mobileer Inc + */ +public class MessageParser { + private int[] parameterIndices = new int[MidiConstants.MAX_CHANNELS]; + private int[] parameterValues = new int[MidiConstants.MAX_CHANNELS]; + private int BIT_NON_RPM = 1 << 14; + private int MASK_14BIT = (1 << 14) - 1; + + public void parse(byte[] message) { + int status = message[0]; + int command = status & 0xF0; + int channel = status & 0x0F; + + switch (command) { + case MidiConstants.NOTE_ON: + int velocity = message[2]; + if (velocity == 0) { + noteOff(channel, message[1], velocity); + } else { + noteOn(channel, message[1], velocity); + } + break; + + case MidiConstants.NOTE_OFF: + noteOff(channel, message[1], message[2]); + break; + + case MidiConstants.POLYPHONIC_AFTERTOUCH: + polyphonicAftertouch(channel, message[1], message[2]); + break; + + case MidiConstants.CHANNEL_PRESSURE: + channelPressure(channel, message[1]); + break; + + case MidiConstants.CONTROL_CHANGE: + rawControlChange(channel, message[1], message[2]); + break; + + case MidiConstants.PROGRAM_CHANGE: + programChange(channel, message[1]); + break; + + case MidiConstants.PITCH_BEND: + int bend = (message[2] << 7) + message[1]; + pitchBend(channel, bend); + break; + } + + } + + public void rawControlChange(int channel, int index, int value) { + int paramIndex; + int paramValue; + switch(index) { + case MidiConstants.CONTROLLER_DATA_ENTRY: + parameterValues[channel] = value << 7; + fireParameterChange(channel); + break; + case MidiConstants.CONTROLLER_DATA_ENTRY_LSB: + paramValue = parameterValues[channel] & ~0x7F; + paramValue |= value; + parameterValues[channel] = paramValue; + fireParameterChange(channel); + break; + case MidiConstants.CONTROLLER_NRPN_LSB: + paramIndex = parameterIndices[channel] & ~0x7F; + paramIndex |= value | BIT_NON_RPM; + parameterIndices[channel] = paramIndex; + break; + case MidiConstants.CONTROLLER_NRPN_MSB: + parameterIndices[channel] = (value << 7) | BIT_NON_RPM;; + break; + case MidiConstants.CONTROLLER_RPN_LSB: + paramIndex = parameterIndices[channel] & ~0x7F; + paramIndex |= value; + parameterIndices[channel] = paramIndex; + break; + case MidiConstants.CONTROLLER_RPN_MSB: + parameterIndices[channel] = value << 7; + break; + default: + controlChange(channel, index, value); + break; + + } + } + + private void fireParameterChange(int channel) { + int paramIndex; + paramIndex = parameterIndices[channel]; + if ((paramIndex & BIT_NON_RPM) == 0) { + registeredParameter(channel, paramIndex, parameterValues[channel]); + } else { + nonRegisteredParameter(channel, paramIndex & MASK_14BIT, parameterValues[channel]); + } + } + + public void nonRegisteredParameter(int channel, int index14, int value14) { + } + + public void registeredParameter(int channel, int index14, int value14) { + } + + public void pitchBend(int channel, int bend) { + } + + public void programChange(int channel, int program) { + } + + public void polyphonicAftertouch(int channel, int pitch, int pressure) { + } + + public void channelPressure(int channel, int pressure) { + } + + public void controlChange(int channel, int index, int value) { + } + + public void noteOn(int channel, int pitch, int velocity) { + } + + // If a NOTE_ON with zero velocity is received then noteOff will be called. + public void noteOff(int channel, int pitch, int velocity) { + } +} diff --git a/src/main/java/com/jsyn/midi/MidiConstants.java b/src/main/java/com/jsyn/midi/MidiConstants.java new file mode 100644 index 0000000..8c92119 --- /dev/null +++ b/src/main/java/com/jsyn/midi/MidiConstants.java @@ -0,0 +1,84 @@ +/* + * Copyright 2010 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.midi; + +/** + * Constants that define the MIDI standard. + * + * @author Phil Burk (C) 2010 Mobileer Inc + */ +public class MidiConstants { + + public static final int MAX_CHANNELS = 16; + // Basic commands. + public static final int NOTE_OFF = 0x80; + public static final int NOTE_ON = 0x90; + public static final int POLYPHONIC_AFTERTOUCH = 0xA0; + public static final int CONTROL_CHANGE = 0xB0; + public static final int PROGRAM_CHANGE = 0xC0; + public static final int CHANNEL_AFTERTOUCH = 0xD0; + public static final int CHANNEL_PRESSURE = CHANNEL_AFTERTOUCH; + public static final int PITCH_BEND = 0xE0; + public static final int SYSTEM_COMMON = 0xF0; + + public static final int PITCH_BEND_CENTER = 0x2000; + + public static final int CONTROLLER_BANK_SELECT = 0; + public static final int CONTROLLER_MOD_WHEEL = 1; + public static final int CONTROLLER_BREATH = 2; + public static final int CONTROLLER_DATA_ENTRY = 6; + public static final int CONTROLLER_VOLUME = 7; + public static final int CONTROLLER_PAN = 10; + + public static final int CONTROLLER_LSB_OFFSET = 32; + public static final int CONTROLLER_DATA_ENTRY_LSB = CONTROLLER_DATA_ENTRY + CONTROLLER_LSB_OFFSET; + + public static final int CONTROLLER_TIMBRE = 74; // Often used by MPE for Y axis control. + + public static final int CONTROLLER_DATA_INCREMENT = 96; + public static final int CONTROLLER_DATA_DECREMENT = 97; + public static final int CONTROLLER_NRPN_LSB = 98; + public static final int CONTROLLER_NRPN_MSB = 99; + public static final int CONTROLLER_RPN_LSB = 100; + public static final int CONTROLLER_RPN_MSB = 101; + + public static final int RPN_BEND_RANGE = 0; + public static final int RPN_FINE_TUNING = 1; + + public static final String PITCH_NAMES[] = { + "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B" + }; + + /** + * Calculate frequency in Hertz based on MIDI pitch. Middle C is 60.0. You can use fractional + * pitches so 60.5 would give you a pitch half way between C and C#. + */ + static final double CONCERT_A_FREQUENCY = 440.0; + static final double CONCERT_A_PITCH = 69.0; + + public static double convertPitchToFrequency(double pitch) { + return CONCERT_A_FREQUENCY * Math.pow(2.0, ((pitch - CONCERT_A_PITCH) / 12.0)); + } + + /** + * Calculate MIDI pitch based on frequency in Hertz. Middle C is 60.0. + */ + public static double convertFrequencyToPitch(double frequency) { + return CONCERT_A_PITCH + (12 * Math.log(frequency / CONCERT_A_FREQUENCY) / Math.log(2.0)); + } + +} diff --git a/src/main/java/com/jsyn/midi/MidiSynthesizer.java b/src/main/java/com/jsyn/midi/MidiSynthesizer.java new file mode 100644 index 0000000..e5dbae7 --- /dev/null +++ b/src/main/java/com/jsyn/midi/MidiSynthesizer.java @@ -0,0 +1,121 @@ +/* + * Copyright 2016 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.midi; + +import com.jsyn.util.MultiChannelSynthesizer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Map MIDI messages into calls to a MultiChannelSynthesizer. + * Handles CONTROLLER_MOD_WHEEL, TIMBRE, VOLUME and PAN. + * Handles Bend Range RPN. + * + *

+    voiceDescription = DualOscillatorSynthVoice.getVoiceDescription();
+    multiSynth = new MultiChannelSynthesizer();
+    final int startChannel = 0;
+    multiSynth.setup(synth, startChannel, NUM_CHANNELS, VOICES_PER_CHANNEL, voiceDescription);
+    midiSynthesizer = new MidiSynthesizer(multiSynth);
+    // pass MIDI bytes
+    midiSynthesizer.onReceive(bytes, 0, bytes.length);
+    
+ * + * See the example UseMidiKeyboard.java + * + * @author Phil Burk (C) 2016 Mobileer Inc + */ +public class MidiSynthesizer extends MessageParser { + + private static final Logger LOGGER = LoggerFactory.getLogger(MidiSynthesizer.class); + + private MultiChannelSynthesizer multiSynth; + + public MidiSynthesizer(MultiChannelSynthesizer multiSynth) { + this.multiSynth = multiSynth; + } + + @Override + public void controlChange(int channel, int index, int value) { + //LOGGER.debug("controlChange(" + channel + ", " + index + ", " + value + ")"); + double normalized = value * (1.0 / 127.0); + switch (index) { + case MidiConstants.CONTROLLER_MOD_WHEEL: + double vibratoDepth = 0.1 * normalized; + LOGGER.debug( "vibratoDepth = " + vibratoDepth ); + multiSynth.setVibratoDepth(channel, vibratoDepth); + break; + case MidiConstants.CONTROLLER_TIMBRE: + multiSynth.setTimbre(channel, normalized); + break; + case MidiConstants.CONTROLLER_VOLUME: + multiSynth.setVolume(channel, normalized); + break; + case MidiConstants.CONTROLLER_PAN: + // convert to -1 to +1 range + multiSynth.setPan(channel, (normalized * 2.0) - 1.0); + break; + } + } + + @Override + public void registeredParameter(int channel, int index14, int value14) { + switch(index14) { + case MidiConstants.RPN_BEND_RANGE: + int semitones = value14 >> 7; + int cents = value14 & 0x7F; + double bendRange = semitones + (cents * 0.01); + multiSynth.setBendRange(channel, bendRange); + break; + default: + break; + } + } + + @Override + public void programChange(int channel, int program) { + multiSynth.programChange(channel, program); + } + + @Override + public void channelPressure(int channel, int value) { + double normalized = value * (1.0 / 127.0); + multiSynth.setPressure(channel, normalized); + } + + @Override + public void noteOff(int channel, int noteNumber, int velocity) { + multiSynth.noteOff(channel, noteNumber, velocity); + } + + @Override + public void noteOn(int channel, int noteNumber, int velocity) { + multiSynth.noteOn(channel, noteNumber, velocity); + } + + @Override + public void pitchBend(int channel, int bend) { + double offset = (bend - MidiConstants.PITCH_BEND_CENTER) + * (1.0 / (MidiConstants.PITCH_BEND_CENTER)); + multiSynth.setPitchBend(channel, offset); + } + + public void onReceive(byte[] bytes, int i, int length) { + parse(bytes); // TODO + } + +} diff --git a/src/main/java/com/jsyn/package.html b/src/main/java/com/jsyn/package.html new file mode 100644 index 0000000..cd73832 --- /dev/null +++ b/src/main/java/com/jsyn/package.html @@ -0,0 +1,17 @@ + + + + +JSyn Package + + +JSyn is a music and audio synthesis API for Java. The basic sequence of operations is: +
    +
  • Use the JSyn class to create a synthesizer.
  • +
  • Create unit generators and add them to the synthesizer.
  • +
  • Connect unit generators so that audio signals can flow between them.
  • +
  • Start an output generator. It will pull data from the connected units.
  • +
  • Set port values and queue sample and envelope data to change the sound.
  • +
+ + \ No newline at end of file diff --git a/src/main/java/com/jsyn/ports/ConnectableInput.java b/src/main/java/com/jsyn/ports/ConnectableInput.java new file mode 100644 index 0000000..3dae876 --- /dev/null +++ b/src/main/java/com/jsyn/ports/ConnectableInput.java @@ -0,0 +1,38 @@ +/* + * Copyright 2011 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; + +/** + * This interface lets you pass either an input port, or a single part of an input port. + * + * @author Phil Burk (C) 2011 Mobileer Inc + */ +public interface ConnectableInput { + public void connect(ConnectableOutput other); + + public void disconnect(ConnectableOutput other); + + /** + * This is used internally by PortBlockPart to make a connection between specific parts of a + * port. + * + * @return + */ + public PortBlockPart getPortBlockPart(); + + public void pullData(long frameCount, int start, int limit); +} diff --git a/src/main/java/com/jsyn/ports/ConnectableOutput.java b/src/main/java/com/jsyn/ports/ConnectableOutput.java new file mode 100644 index 0000000..f42a799 --- /dev/null +++ b/src/main/java/com/jsyn/ports/ConnectableOutput.java @@ -0,0 +1,23 @@ +/* + * 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; + +public interface ConnectableOutput { + public void connect(ConnectableInput other); + + public void disconnect(ConnectableInput other); +} diff --git a/src/main/java/com/jsyn/ports/GettablePort.java b/src/main/java/com/jsyn/ports/GettablePort.java new file mode 100644 index 0000000..aabf5ca --- /dev/null +++ b/src/main/java/com/jsyn/ports/GettablePort.java @@ -0,0 +1,27 @@ +/* + * 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; + +public interface GettablePort { + String getName(); + + int getNumParts(); + + double getValue(int partNum); + + Object getUnitGenerator(); +} diff --git a/src/main/java/com/jsyn/ports/InputMixingBlockPart.java b/src/main/java/com/jsyn/ports/InputMixingBlockPart.java new file mode 100644 index 0000000..2d28888 --- /dev/null +++ b/src/main/java/com/jsyn/ports/InputMixingBlockPart.java @@ -0,0 +1,112 @@ +/* + * 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 java.io.PrintStream; + +import com.jsyn.Synthesizer; +import com.jsyn.unitgen.UnitGenerator; + +/** + * A UnitInputPort has an array of these, one for each part. + * + * @author Phil Burk 2009 Mobileer Inc + */ + +public class InputMixingBlockPart extends PortBlockPart { + private double[] mixer = new double[Synthesizer.FRAMES_PER_BLOCK]; + private double current; + private UnitInputPort unitInputPort; + + InputMixingBlockPart(UnitInputPort unitInputPort, double defaultValue) { + super(unitInputPort, defaultValue); + this.unitInputPort = unitInputPort; + } + + @Override + public double getValue() { + return current; + } + + @Override + protected void setValue(double value) { + current = value; + super.setValue(value); + } + + @Override + public double[] getValues() { + double[] result; + int numConnections = getConnectionCount(); + // LOGGER.debug("numConnection = " + numConnections + " for " + + // this ); + if (numConnections == 0) { + // No connection so just use our own data. + result = super.getValues(); + } else { + // Mix all of the connected ports. + double[] inputs; + int jCon = 0; + PortBlockPart otherPart; + // Choose value to initialize the mixer array. + if (unitInputPort.isValueAdded()) { + inputs = super.getValues(); // prime mixer with the set() values + jCon = 0; + } else { + otherPart = getConnection(jCon); + inputs = otherPart.getValues(); // prime mixer with first connected + jCon = 1; + } + for (int i = 0; i < mixer.length; i++) { + mixer[i] = inputs[i]; + } + // Now mix in the remaining inputs. + for (; jCon < numConnections; jCon++) { + otherPart = getConnection(jCon); + inputs = otherPart.getValues(); + for (int i = 0; i < mixer.length; i++) { + mixer[i] += inputs[i]; + } + } + result = mixer; + } + current = result[0]; + return result; + } + + private void printIndentation(PrintStream out, int level) { + for (int i = 0; i < level; i++) { + out.print(" "); + } + } + + private String portToString(UnitBlockPort port) { + UnitGenerator ugen = port.getUnitGenerator(); + return ugen.getClass().getSimpleName() + "." + port.getName(); + } + + public void printConnections(PrintStream out, int level) { + for (int i = 0; i < getConnectionCount(); i++) { + PortBlockPart part = getConnection(i); + + printIndentation(out, level); + out.println(portToString(getPort()) + " <--- " + portToString(part.getPort())); + + part.getPort().getUnitGenerator().printConnections(out, level + 1); + } + } +} diff --git a/src/main/java/com/jsyn/ports/PortBlockPart.java b/src/main/java/com/jsyn/ports/PortBlockPart.java new file mode 100644 index 0000000..b1ced32 --- /dev/null +++ b/src/main/java/com/jsyn/ports/PortBlockPart.java @@ -0,0 +1,210 @@ +/* + * 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 java.util.ArrayList; + +import com.jsyn.Synthesizer; +import com.jsyn.engine.SynthesisEngine; +import com.softsynth.shared.time.ScheduledCommand; +import com.softsynth.shared.time.TimeStamp; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Part of a multi-part port, for example, the left side of a stereo port. + * + * @author Phil Burk (C) 2009 Mobileer Inc + */ +public class PortBlockPart implements ConnectableOutput, ConnectableInput { + + private static final Logger LOGGER = LoggerFactory.getLogger(PortBlockPart.class); + + private double[] values = new double[Synthesizer.FRAMES_PER_BLOCK]; + private ArrayList connections = new ArrayList(); + private UnitBlockPort unitBlockPort; + + protected PortBlockPart(UnitBlockPort unitBlockPort, double defaultValue) { + this.unitBlockPort = unitBlockPort; + setValue(defaultValue); + } + + public double[] getValues() { + return values; + } + + public double getValue() { + return values[0]; + } + + public double get() { + return values[0]; + } + + protected void setValue(double value) { + for (int i = 0; i < values.length; i++) { + values[i] = value; + } + } + + protected boolean isConnected() { + return (connections.size() > 0); + } + + private void addConnection(PortBlockPart otherPart) { + // LOGGER.debug("addConnection from " + this + " to " + otherPart + // ); + if (connections.contains(otherPart)) { + LOGGER.debug("addConnection already had connection from " + this + " to " + + otherPart); + } else { + connections.add(otherPart); + } + } + + private void removeConnection(PortBlockPart otherPart) { + // LOGGER.debug("removeConnection from " + this + " to " + + // otherPart ); + connections.remove(otherPart); + } + + private void connectNow(PortBlockPart otherPart) { + addConnection(otherPart); + otherPart.addConnection(this); + } + + private void disconnectNow(PortBlockPart otherPart) { + removeConnection(otherPart); + otherPart.removeConnection(this); + } + + private void disconnectAllNow() { + for (PortBlockPart part : connections) { + part.removeConnection(this); + } + connections.clear(); + } + + public PortBlockPart getConnection(int i) { + return connections.get(i); + } + + public int getConnectionCount() { + return connections.size(); + } + + /** Set all values to the last value. */ + protected void flatten() { + double lastValue = values[values.length - 1]; + for (int i = 0; i < values.length - 1; i++) { + values[i] = lastValue; + } + } + + protected UnitBlockPort getPort() { + return unitBlockPort; + } + + private void checkConnection(PortBlockPart destination) { + SynthesisEngine sourceSynth = unitBlockPort.getSynthesisEngine(); + SynthesisEngine destSynth = destination.unitBlockPort.getSynthesisEngine(); + if ((sourceSynth != destSynth) && (sourceSynth != null) && (destSynth != null)) { + throw new RuntimeException("Connection between units on different synths."); + } + } + + protected void connect(final PortBlockPart destination) { + checkConnection(destination); + unitBlockPort.queueCommand(new ScheduledCommand() { + @Override + public void run() { + connectNow(destination); + } + }); + } + + protected void connect(final PortBlockPart destination, TimeStamp timeStamp) { + unitBlockPort.scheduleCommand(timeStamp, new ScheduledCommand() { + @Override + public void run() { + connectNow(destination); + } + }); + } + + protected void disconnect(final PortBlockPart destination) { + unitBlockPort.queueCommand(new ScheduledCommand() { + @Override + public void run() { + disconnectNow(destination); + } + }); + } + + protected void disconnect(final PortBlockPart destination, TimeStamp timeStamp) { + unitBlockPort.scheduleCommand(timeStamp, new ScheduledCommand() { + @Override + public void run() { + disconnectNow(destination); + } + }); + } + + protected void disconnectAll() { + unitBlockPort.queueCommand(new ScheduledCommand() { + @Override + public void run() { + disconnectAllNow(); + } + }); + } + + @Override + public void connect(ConnectableInput other) { + connect(other.getPortBlockPart()); + } + + @Override + public void connect(ConnectableOutput other) { + other.connect(this); + } + + @Override + public void disconnect(ConnectableOutput other) { + other.disconnect(this); + } + + @Override + public void disconnect(ConnectableInput other) { + disconnect(other.getPortBlockPart()); + } + + /** To implement ConnectableInput */ + @Override + public PortBlockPart getPortBlockPart() { + return this; + } + + @Override + public void pullData(long frameCount, int start, int limit) { + for (int i = 0; i < getConnectionCount(); i++) { + PortBlockPart part = getConnection(i); + part.getPort().getUnitGenerator().pullData(frameCount, start, limit); + } + } + +} diff --git a/src/main/java/com/jsyn/ports/QueueDataCommand.java b/src/main/java/com/jsyn/ports/QueueDataCommand.java new file mode 100644 index 0000000..0ef36e2 --- /dev/null +++ b/src/main/java/com/jsyn/ports/QueueDataCommand.java @@ -0,0 +1,170 @@ +/* + * 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.softsynth.shared.time.ScheduledCommand; + +/** + * A command that can be used to queue SequentialData to a UnitDataQueuePort. Here is an example of + * queuing data with a callback using this command. + * + *
+ * 
+ * 	// Queue an envelope with a completion callback.
+ * 	QueueDataCommand command = envelopePlayer.dataQueue.createQueueDataCommand( envelope, 0,
+ * 			envelope.getNumFrames() );
+ * 	// Create an object to be called when the queued data is done.
+ * 	TestQueueCallback callback = new TestQueueCallback();
+ * 	command.setCallback( callback );
+ * 	command.setNumLoops( 2 );
+ * 	envelopePlayer.rate.set( 0.2 );
+ * 	synth.queueCommand( command );
+ *  
+ * 
+ * + * The callback will be passed QueueDataEvents. + * + *
+ * 
+ * 	class TestQueueCallback implements UnitDataQueueCallback
+ * 	{
+ * 		public void started( QueueDataEvent event )
+ * 		{
+ * 			LOGGER.debug("CALLBACK: Envelope started.");
+ * 		}
+ *
+ * 		public void looped( QueueDataEvent event )
+ * 		{
+ * 			LOGGER.debug("CALLBACK: Envelope looped.");
+ * 		}
+ *
+ * 		public void finished( QueueDataEvent event )
+ * 		{
+ * 			LOGGER.debug("CALLBACK: Envelope finished.");
+ * 		}
+ * 	}
+ * 
+ * 
+ * + * @author Phil Burk 2009 Mobileer Inc + */ +public abstract class QueueDataCommand extends QueueDataEvent implements ScheduledCommand { + + protected SequentialDataCrossfade crossfadeData; + protected SequentialData currentData; + + private static final long serialVersionUID = -1185274459972359536L; + private UnitDataQueueCallback callback; + + public QueueDataCommand(UnitDataQueuePort port, SequentialData sequentialData, int startFrame, + int numFrames) { + super(port); + + if ((startFrame + numFrames) > sequentialData.getNumFrames()) { + throw new IllegalArgumentException("tried to queue past end of data, " + (startFrame + numFrames)); + } else if (startFrame < 0) { + throw new IllegalArgumentException("tried to queue before start of data, " + startFrame); + } + this.sequentialData = sequentialData; + this.currentData = sequentialData; + crossfadeData = new SequentialDataCrossfade(); + this.startFrame = startFrame; + this.numFrames = numFrames; + } + + @Override + public abstract void run(); + + /** + * If true then this item will be skipped if other items are queued after it. This flag allows + * you to queue lots of small pieces of sound without making the queue very long. + * + * @param skipIfOthers + */ + public void setSkipIfOthers(boolean skipIfOthers) { + this.skipIfOthers = skipIfOthers; + } + + /** + * If true then the queue will be cleared and this item will be started immediately. It is + * better to use this flag than to clear the queue from the application because there could be a + * gap before the next item is available. This is most useful when combined with + * setCrossFadeIn(). + * + * @param immediate + */ + public void setImmediate(boolean immediate) { + this.immediate = immediate; + } + + public UnitDataQueueCallback getCallback() { + return callback; + } + + public void setCallback(UnitDataQueueCallback callback) { + this.callback = callback; + } + + public SequentialDataCrossfade getCrossfadeData() { + return crossfadeData; + } + + public void setCrossfadeData(SequentialDataCrossfade crossfadeData) { + this.crossfadeData = crossfadeData; + } + + public SequentialData getCurrentData() { + return currentData; + } + + public void setCurrentData(SequentialData currentData) { + this.currentData = currentData; + } + + /** + * Stop the unit that contains this port after this command has finished. + * + * @param autoStop + */ + public void setAutoStop(boolean autoStop) { + this.autoStop = autoStop; + } + + /** + * Set how many time the block should be repeated after the first time. For example, if you set + * numLoops to zero the block will only be played once. If you set numLoops to one the block + * will be played twice. + * + * @param numLoops number of times to loop back + */ + public void setNumLoops(int numLoops) { + this.numLoops = numLoops; + } + + /** + * Number of frames to cross fade from the previous block to this block. This can be used to + * avoid pops when making abrupt transitions. There must be frames available after the end of + * the previous block to use for crossfading. The crossfade is linear. + * + * @param size + */ + public void setCrossFadeIn(int size) { + this.crossFadeIn = size; + } + +} diff --git a/src/main/java/com/jsyn/ports/QueueDataEvent.java b/src/main/java/com/jsyn/ports/QueueDataEvent.java new file mode 100644 index 0000000..2b93fab --- /dev/null +++ b/src/main/java/com/jsyn/ports/QueueDataEvent.java @@ -0,0 +1,80 @@ +/* + * 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 java.util.EventObject; + +import com.jsyn.data.SequentialData; + +/** + * An event that is passed to a UnitDataQueueCallback when the element in the queue is played.. + * + * @author Phil Burk 2009 Mobileer Inc + */ +public class QueueDataEvent extends EventObject { + private static final long serialVersionUID = 176846633064538053L; + protected SequentialData sequentialData; + protected int startFrame; + protected int numFrames; + protected int numLoops; + protected int loopsLeft; + protected int crossFadeIn; + protected boolean skipIfOthers; + protected boolean autoStop; + protected boolean immediate; + + public QueueDataEvent(Object arg0) { + super(arg0); + } + + public boolean isSkipIfOthers() { + return skipIfOthers; + } + + public boolean isImmediate() { + return immediate; + } + + public SequentialData getSequentialData() { + return sequentialData; + } + + public int getCrossFadeIn() { + return crossFadeIn; + } + + public int getStartFrame() { + return startFrame; + } + + public int getNumFrames() { + return numFrames; + } + + public int getNumLoops() { + return numLoops; + } + + public int getLoopsLeft() { + return loopsLeft; + } + + public boolean isAutoStop() { + return autoStop; + } + +} diff --git a/src/main/java/com/jsyn/ports/SequentialDataCrossfade.java b/src/main/java/com/jsyn/ports/SequentialDataCrossfade.java new file mode 100644 index 0000000..0c3d3b2 --- /dev/null +++ b/src/main/java/com/jsyn/ports/SequentialDataCrossfade.java @@ -0,0 +1,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: + * + *
+ * SequentialDataCrossfade xfade = new SequentialDataCrossfade(sample, (50000 + 30000), 5000, sample,
+ *         50000, 30000);
+ * 
+ * + * 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() + *
    + *
  1. Crossfade region with size crossFadeFrames. It fades smoothly from source to target.
  2. + *
  3. Steady region that is simply the target values with size (numFrames-crossFadeFrames).
  4. + *
+ * + *
+ *     "Crossfade Region"      "Steady Region"
+ * |-- source fading out --|
+ * |-- target fading in  --|-- remainder of target at original volume --|
+ * 
+ * + * @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()); + + // LOGGER.debug( "WARNING! sourceStartFrame = " + sourceStartFrame + // + ", crossFadeFrames = " + crossFadeFrames + ", maxFrame = " + // + source.getNumFrames() + ", source = " + source ); + // LOGGER.debug( " 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(); + } + +} diff --git a/src/main/java/com/jsyn/ports/SettablePort.java b/src/main/java/com/jsyn/ports/SettablePort.java new file mode 100644 index 0000000..e0db05c --- /dev/null +++ b/src/main/java/com/jsyn/ports/SettablePort.java @@ -0,0 +1,28 @@ +/* + * 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.softsynth.shared.time.TimeStamp; + +/** + * Port whose parts can be set. + * + * @author Phil Burk (C) 2009 Mobileer Inc + */ +public interface SettablePort extends GettablePort { + void set(int partNum, double value, TimeStamp timeStamp); +} diff --git a/src/main/java/com/jsyn/ports/UnitBlockPort.java b/src/main/java/com/jsyn/ports/UnitBlockPort.java new file mode 100644 index 0000000..d7fc82f --- /dev/null +++ b/src/main/java/com/jsyn/ports/UnitBlockPort.java @@ -0,0 +1,110 @@ +/* + * 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; + +/** + * A port that contains multiple parts with blocks of data. + * + * @author Phil Burk 2009 Mobileer Inc + */ +public class UnitBlockPort extends UnitPort { + PortBlockPart[] parts; + + public UnitBlockPort(int numParts, String name, double defaultValue) { + super(name); + makeParts(numParts, defaultValue); + } + + public UnitBlockPort(String name) { + this(1, name, 0.0); + } + + protected void makeParts(int numParts, double defaultValue) { + parts = new PortBlockPart[numParts]; + for (int i = 0; i < numParts; i++) { + parts[i] = new PortBlockPart(this, defaultValue); + } + } + + @Override + public int getNumParts() { + return parts.length; + } + + /** + * Convenience call to get(0). + * + * @return value of 0th part as set + */ + public double get() { + return get(0); + } + + public double getValue() { + return getValue(0); + } + + /** + * This is used inside UnitGenerators to get the current values for a port. It works regardless + * of whether the port is connected or not. + * + * @return + */ + public double[] getValues() { + return parts[0].getValues(); + } + + /** Only for use in the audio thread when implementing UnitGenerators. */ + public double[] getValues(int partNum) { + return parts[partNum].getValues(); + } + + /** Get the immediate current value of the port. */ + public double getValue(int partNum) { + return parts[partNum].getValue(); + } + + public double get(int partNum) { + return parts[partNum].get(); + } + + /** Only for use in the audio thread when implementing UnitGenerators. */ + protected void setValueInternal(int partNum, double value) { + parts[partNum].setValue(value); + } + + /** Only for use in the audio thread when implementing UnitGenerators. */ + public void setValueInternal(double value) { + setValueInternal(0, value); + } + + public boolean isConnected() { + return isConnected(0); + } + + public boolean isConnected(int partNum) { + return parts[partNum].isConnected(); + } + + public void disconnectAll(int partNum) { + parts[partNum].disconnectAll(); + } + + public void disconnectAll() { + disconnectAll(0); + } +} diff --git a/src/main/java/com/jsyn/ports/UnitDataQueueCallback.java b/src/main/java/com/jsyn/ports/UnitDataQueueCallback.java new file mode 100644 index 0000000..dca4adc --- /dev/null +++ b/src/main/java/com/jsyn/ports/UnitDataQueueCallback.java @@ -0,0 +1,31 @@ +/* + * Copyright 2011 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; + +/** + * This is called when a block of data that is queued to a UnitDataQueuePort starts, loops, or + * finishes. + * + * @author Phil Burk (C) 2011 Mobileer Inc + */ +public interface UnitDataQueueCallback { + public void started(QueueDataEvent event); + + public void looped(QueueDataEvent event); + + public void finished(QueueDataEvent event); +} diff --git a/src/main/java/com/jsyn/ports/UnitDataQueuePort.java b/src/main/java/com/jsyn/ports/UnitDataQueuePort.java new file mode 100644 index 0000000..13b2e2a --- /dev/null +++ b/src/main/java/com/jsyn/ports/UnitDataQueuePort.java @@ -0,0 +1,466 @@ +/* + * 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 java.util.LinkedList; + +import com.jsyn.data.SequentialData; +import com.jsyn.exceptions.ChannelMismatchException; +import com.softsynth.shared.time.ScheduledCommand; +import com.softsynth.shared.time.TimeStamp; + +/** + * Queue for SequentialData, samples or envelopes + * + * @author Phil Burk (C) 2009 Mobileer Inc + */ +public class UnitDataQueuePort extends UnitPort { + private final LinkedList blocks = new LinkedList(); + private QueueDataCommand currentBlock; + private int frameIndex; + private int numChannels = 1; + private double normalizedRate; + private long framesMoved; + private boolean autoStopPending; + private boolean targetValid; + private QueueDataCommand finishingBlock; + private QueueDataCommand loopingBlock; + public static final int LOOP_IF_LAST = -1; + + public UnitDataQueuePort(String name) { + super(name); + } + + /** Hold a reference to part of a sample. */ + @SuppressWarnings("serial") + private class QueuedBlock extends QueueDataCommand { + + public QueuedBlock(SequentialData queueableData, int startFrame, int numFrames) { + super(UnitDataQueuePort.this, queueableData, startFrame, numFrames); + } + + @Override + public void run() { + synchronized (blocks) { + // Remove last block if it can be skipped. + if (blocks.size() > 0) { + QueueDataEvent lastBlock = blocks.getLast(); + if (lastBlock.isSkipIfOthers()) { + blocks.removeLast(); + } + } + + // If we are crossfading then figure out where to crossfade + // from. + if (getCrossFadeIn() > 0) { + if (isImmediate()) { + // Queue will be cleared so fade in from current. + if (currentBlock != null) { + setupCrossFade(currentBlock, frameIndex, this); + } + // else nothing is playing so don't crossfade. + } else { + QueueDataCommand endBlock = getEndBlock(); + if (endBlock != null) { + setupCrossFade(endBlock, + endBlock.getStartFrame() + endBlock.getNumFrames(), this); + } + } + } + + if (isImmediate()) { + clearQueue(); + } + + blocks.add(this); + } + } + } + + // FIXME - determine crossfade on any transition between blocks or when looping back. + + protected void setupCrossFade(QueueDataCommand sourceCommand, int sourceStartIndex, + QueueDataCommand targetCommand) { + int crossFrames = targetCommand.getCrossFadeIn(); + SequentialData sourceData = sourceCommand.getCurrentData(); + SequentialData targetData = targetCommand.getCurrentData(); + int remainingSource = sourceData.getNumFrames() - sourceStartIndex; + // clip to end of source + if (crossFrames > remainingSource) + crossFrames = remainingSource; + if (crossFrames > 0) { + // The SequentialDataCrossfade should continue to the end of the target + // so that we can crossfade from it to the target. + int remainingTarget = targetData.getNumFrames() - targetCommand.getStartFrame(); + targetCommand.crossfadeData.setup(sourceData, sourceStartIndex, crossFrames, + targetData, targetCommand.getStartFrame(), remainingTarget); + targetCommand.currentData = targetCommand.crossfadeData; + targetCommand.startFrame = 0; + } + } + + public QueueDataCommand createQueueDataCommand(SequentialData queueableData) { + return createQueueDataCommand(queueableData, 0, queueableData.getNumFrames()); + } + + public QueueDataCommand createQueueDataCommand(SequentialData queueableData, int startFrame, + int numFrames) { + if (queueableData.getChannelsPerFrame() != UnitDataQueuePort.this.numChannels) { + throw new ChannelMismatchException("Tried to queue " + + queueableData.getChannelsPerFrame() + " channel data to a " + numChannels + + " channel port."); + } + return new QueuedBlock(queueableData, startFrame, numFrames); + } + + public QueueDataCommand getEndBlock() { + if (blocks.size() > 0) { + return blocks.getLast(); + } else if (currentBlock != null) { + return currentBlock; + } else { + return null; + } + } + + public void setCurrentBlock(QueueDataCommand currentBlock) { + this.currentBlock = currentBlock; + } + + public void firePendingCallbacks() { + if (loopingBlock != null) { + if (loopingBlock.getCallback() != null) { + loopingBlock.getCallback().looped(currentBlock); + } + loopingBlock = null; + } + if (finishingBlock != null) { + if (finishingBlock.getCallback() != null) { + finishingBlock.getCallback().finished(currentBlock); // FIXME - Should this pass + // finishingBlock?! + } + finishingBlock = null; + } + } + + public boolean hasMore() { + return (currentBlock != null) || (blocks.size() > 0); + } + + private void checkBlock() { + if (currentBlock == null) { + synchronized (blocks) { + setCurrentBlock(blocks.remove()); + frameIndex = currentBlock.getStartFrame(); + currentBlock.loopsLeft = currentBlock.getNumLoops(); + if (currentBlock.getCallback() != null) { + currentBlock.getCallback().started(currentBlock); + } + } + } + } + + private void advanceFrameIndex() { + frameIndex += 1; + framesMoved += 1; + // Are we done with this block? + if (frameIndex >= (currentBlock.getStartFrame() + currentBlock.getNumFrames())) { + // Should we loop on this block based on a counter? + if (currentBlock.loopsLeft > 0) { + currentBlock.loopsLeft -= 1; + loopToStart(); + } + // Should we loop forever on this block? + else if ((blocks.size() == 0) && (currentBlock.loopsLeft < 0)) { + loopToStart(); + } + // We are done. + else { + if (currentBlock.isAutoStop()) { + autoStopPending = true; + } + finishingBlock = currentBlock; + setCurrentBlock(null); + // LOGGER.debug("advanceFrameIndex: currentBlock set null"); + } + } + } + + private void loopToStart() { + if (currentBlock.getCrossFadeIn() > 0) { + setupCrossFade(currentBlock, frameIndex, currentBlock); + } + frameIndex = currentBlock.getStartFrame(); + loopingBlock = currentBlock; + } + + public double getNormalizedRate() { + return normalizedRate; + } + + public double readCurrentChannelDouble(int channelIndex) { + return currentBlock.currentData.readDouble((frameIndex * numChannels) + channelIndex); + } + + public void writeCurrentChannelDouble(int channelIndex, double value) { + currentBlock.currentData.writeDouble((frameIndex * numChannels) + channelIndex, value); + } + + public void beginFrame(double synthesisPeriod) { + checkBlock(); + normalizedRate = currentBlock.currentData.getRateScaler(frameIndex, synthesisPeriod); + } + + public void endFrame() { + advanceFrameIndex(); + targetValid = true; + } + + public double readNextMonoDouble(double synthesisPeriod) { + beginFrame(synthesisPeriod); + double value = currentBlock.currentData.readDouble(frameIndex); + endFrame(); + return value; + } + + /** Write directly to the port queue. This is only called by unit tests! */ + protected void addQueuedBlock(QueueDataEvent block) { + blocks.add((QueuedBlock) block); + } + + /** Clear the queue. Internal use only. */ + protected void clearQueue() { + synchronized (blocks) { + blocks.clear(); + setCurrentBlock(null); + targetValid = false; + autoStopPending = false; + } + } + + class ClearQueueCommand implements ScheduledCommand { + @Override + public void run() { + clearQueue(); + } + } + + /** Queue the data to the port at a future time. */ + public void queue(SequentialData queueableData, int startFrame, int numFrames, + TimeStamp timeStamp) { + QueueDataCommand command = createQueueDataCommand(queueableData, startFrame, numFrames); + scheduleCommand(timeStamp, command); + } + + /** + * Queue the data to the port at a future time. Command will clear the queue before executing. + */ + public void queueImmediate(SequentialData queueableData, int startFrame, int numFrames, + TimeStamp timeStamp) { + QueueDataCommand command = createQueueDataCommand(queueableData, startFrame, numFrames); + command.setImmediate(true); + scheduleCommand(timeStamp, command); + } + + /** Queue the data to the port at a future time. */ + public void queueLoop(SequentialData queueableData, int startFrame, int numFrames, + TimeStamp timeStamp) { + queueLoop(queueableData, startFrame, numFrames, LOOP_IF_LAST, timeStamp); + } + + /** + * Queue the data to the port at a future time with a specified number of loops. + */ + public void queueLoop(SequentialData queueableData, int startFrame, int numFrames, + int numLoops, TimeStamp timeStamp) { + QueueDataCommand command = createQueueDataCommand(queueableData, startFrame, numFrames); + command.setNumLoops(numLoops); + scheduleCommand(timeStamp, command); + } + + /** Queue the entire data object for looping. */ + public void queueLoop(SequentialData queueableData) { + queueLoop(queueableData, 0, queueableData.getNumFrames()); + } + + /** Queue the data to the port for immediate use. */ + public void queueLoop(SequentialData queueableData, int startFrame, int numFrames) { + queueLoop(queueableData, startFrame, numFrames, LOOP_IF_LAST); + } + + /** + * Queue the data to the port for immediate use with a specified number of loops. + */ + public void queueLoop(SequentialData queueableData, int startFrame, int numFrames, int numLoops) { + QueueDataCommand command = createQueueDataCommand(queueableData, startFrame, numFrames); + command.setNumLoops(numLoops); + queueCommand(command); + } + + /** + * Queue the data to the port at a future time. Request that the unit stop when this block is + * finished. + */ + public void queueStop(SequentialData queueableData, int startFrame, int numFrames, + TimeStamp timeStamp) { + QueueDataCommand command = createQueueDataCommand(queueableData, startFrame, numFrames); + command.setAutoStop(true); + scheduleCommand(timeStamp, command); + } + + /** Queue the data to the port through the command queue ASAP. */ + public void queue(SequentialData queueableData, int startFrame, int numFrames) { + QueueDataCommand command = createQueueDataCommand(queueableData, startFrame, numFrames); + queueCommand(command); + } + + /** + * Queue entire amount of data with no options. + * + * @param queueableData + */ + public void queue(SequentialData queueableData) { + queue(queueableData, 0, queueableData.getNumFrames()); + } + + /** Schedule queueOn now! */ + public void queueOn(SequentialData queueableData) { + queueOn(queueableData, getSynthesisEngine().createTimeStamp()); + } + + /** Schedule queueOff now! */ + public void queueOff(SequentialData queueableData) { + queueOff(queueableData, false); + } + + /** Schedule queueOff now! */ + public void queueOff(SequentialData queueableData, boolean ifStop) { + queueOff(queueableData, ifStop, getSynthesisEngine().createTimeStamp()); + } + + /** + * Convenience method that will queue the attack portion of a channelData and the sustain loop + * if it exists. This could be used to implement a NoteOn method. + */ + public void queueOn(SequentialData queueableData, TimeStamp timeStamp) { + + if (queueableData.getSustainBegin() < 0) { + // no sustain loop, handle release + if (queueableData.getReleaseBegin() < 0) { + // No loops + queueImmediate(queueableData, 0, queueableData.getNumFrames(), timeStamp); + } else { + queueImmediate(queueableData, 0, queueableData.getReleaseEnd(), timeStamp); + int size = queueableData.getReleaseEnd() - queueableData.getReleaseBegin(); + queueLoop(queueableData, queueableData.getReleaseBegin(), size, timeStamp); + } + } else { + // yes sustain loop + if (queueableData.getSustainEnd() > 0) { + int frontSize = queueableData.getSustainBegin(); + int loopSize = queueableData.getSustainEnd() - queueableData.getSustainBegin(); + // Is there an initial portion before the sustain loop? + if (frontSize > 0) { + queueImmediate(queueableData, 0, frontSize, timeStamp); + } + loopSize = queueableData.getSustainEnd() - queueableData.getSustainBegin(); + if (loopSize > 0) { + queueLoop(queueableData, queueableData.getSustainBegin(), loopSize, timeStamp); + } + } + + } + } + + /** + * Convenience method that will queue the decay portion of a SequentialData object, or the gap + * and release loop portions if they exist. This could be used to implement a NoteOff method. + * + * @param ifStop Will setAutostop(true) if release portion queued without a release loop. This will + * stop execution of the unit. + */ + public void queueOff(SequentialData queueableData, boolean ifStop, TimeStamp timeStamp) { + if (queueableData.getSustainBegin() >= 0) /* Sustain loop? */ + { + int relSize = queueableData.getReleaseEnd() - queueableData.getReleaseBegin(); + if (queueableData.getReleaseBegin() < 0) { /* Sustain loop, no release loop. */ + int susEnd = queueableData.getSustainEnd(); + int size = queueableData.getNumFrames() - susEnd; + // LOGGER.debug("queueOff: size = " + size ); + if (size <= 0) { + // always queue something so that we can stop the loop + // 20001117 + size = 1; + susEnd = queueableData.getNumFrames() - 1; + } + if (ifStop) { + queueStop(queueableData, susEnd, size, timeStamp); + } else { + queue(queueableData, susEnd, size, timeStamp); + } + } else if (queueableData.getReleaseBegin() > queueableData.getSustainEnd()) { + // Queue gap between sustain and release loop. + queue(queueableData, queueableData.getSustainEnd(), queueableData.getReleaseEnd() + - queueableData.getSustainEnd(), timeStamp); + if (relSize > 0) + queueLoop(queueableData, queueableData.getReleaseBegin(), relSize, timeStamp); + } else if (relSize > 0) { + // No gap between sustain and release. + queueLoop(queueableData, queueableData.getReleaseBegin(), relSize, timeStamp); + } + } + /* If no sustain loop, then nothing to do. */ + } + + public void clear(TimeStamp timeStamp) { + ScheduledCommand command = new ClearQueueCommand(); + scheduleCommand(timeStamp, command); + } + + public void clear() { + ScheduledCommand command = new ClearQueueCommand(); + queueCommand(command); + } + + public void writeNextDouble(double value) { + checkBlock(); + currentBlock.currentData.writeDouble(frameIndex, value); + advanceFrameIndex(); + } + + public long getFrameCount() { + return framesMoved; + } + + public boolean testAndClearAutoStop() { + boolean temp = autoStopPending; + autoStopPending = false; + return temp; + } + + public boolean isTargetValid() { + return targetValid; + } + + public void setNumChannels(int numChannels) { + this.numChannels = numChannels; + } + + public int getNumChannels() { + return numChannels; + } +} diff --git a/src/main/java/com/jsyn/ports/UnitFunctionPort.java b/src/main/java/com/jsyn/ports/UnitFunctionPort.java new file mode 100644 index 0000000..e45241a --- /dev/null +++ b/src/main/java/com/jsyn/ports/UnitFunctionPort.java @@ -0,0 +1,48 @@ +/* + * 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.Function; + +/** + * Port for holding a Function object. + * + * @author Phil Burk (C) 2009 Mobileer Inc + */ +public class UnitFunctionPort extends UnitPort { + private static NullFunction nullFunction = new NullFunction(); + private Function function = nullFunction; + + private static class NullFunction implements Function { + @Override + public double evaluate(double input) { + return 0.0; + } + } + + public UnitFunctionPort(String name) { + super(name); + } + + public void set(Function function) { + this.function = function; + } + + public Function get() { + return function; + } +} diff --git a/src/main/java/com/jsyn/ports/UnitGatePort.java b/src/main/java/com/jsyn/ports/UnitGatePort.java new file mode 100644 index 0000000..700aef8 --- /dev/null +++ b/src/main/java/com/jsyn/ports/UnitGatePort.java @@ -0,0 +1,158 @@ +/* + * 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.unitgen.UnitGenerator; +import com.softsynth.shared.time.ScheduledCommand; +import com.softsynth.shared.time.TimeStamp; + +public class UnitGatePort extends UnitInputPort { + private boolean autoDisableEnabled = false; + private boolean triggered = false; + private boolean off = true; + private UnitGenerator gatedUnit; + public static final double THRESHOLD = 0.01; + + public UnitGatePort(String name) { + super(name); + } + + public void on() { + setOn(true); + } + + public void off() { + setOn(false); + } + + public void off(TimeStamp timeStamp) { + setOn(false, timeStamp); + } + + public void on(TimeStamp timeStamp) { + setOn(true, timeStamp); + } + + private void setOn(final boolean on) { + queueCommand(new ScheduledCommand() { + @Override + public void run() { + setOnInternal(on); + } + }); + } + + private void setOn(final boolean on, TimeStamp timeStamp) { + scheduleCommand(timeStamp, new ScheduledCommand() { + @Override + public void run() { + setOnInternal(on); + } + }); + } + + private void setOnInternal(boolean on) { + if (on) { + triggerInternal(); + } + setValueInternal(on ? 1.0 : 0.0); + } + + private void triggerInternal() { + getGatedUnit().setEnabled(true); + triggered = true; + } + + public void trigger() { + queueCommand(new ScheduledCommand() { + @Override + public void run() { + triggerInternal(); + } + }); + } + + public void trigger(TimeStamp timeStamp) { + scheduleCommand(timeStamp, new ScheduledCommand() { + @Override + public void run() { + triggerInternal(); + } + }); + } + + /** + * This is called by UnitGenerators. It sets the off value that can be tested using isOff(). + * + * @param i + * @return true if triggered by a positive edge. + */ + public boolean checkGate(int i) { + double[] inputs = getValues(); + boolean result = triggered; + triggered = false; + if (off) { + if (inputs[i] >= THRESHOLD) { + result = true; + off = false; + } + } else { + if (inputs[i] < THRESHOLD) { + off = true; + } + } + return result; + } + + public boolean isOff() { + return off; + } + + public boolean isAutoDisableEnabled() { + return autoDisableEnabled; + } + + /** + * Request the containing UnitGenerator be disabled when checkAutoDisabled() is called. This can + * be used to reduce CPU load. + * + * @param autoDisableEnabled + */ + public void setAutoDisableEnabled(boolean autoDisableEnabled) { + this.autoDisableEnabled = autoDisableEnabled; + } + + /** + * Called by UnitGenerator when an envelope reaches the end of its contour. + */ + public void checkAutoDisable() { + if (autoDisableEnabled) { + getGatedUnit().setEnabled(false); + } + } + + private UnitGenerator getGatedUnit() { + return (gatedUnit == null) ? getUnitGenerator() : gatedUnit; + } + + public void setupAutoDisable(UnitGenerator unit) { + gatedUnit = unit; + setAutoDisableEnabled(true); + // Start off disabled so we don't immediately swamp the CPU. + gatedUnit.setEnabled(false); + } +} diff --git a/src/main/java/com/jsyn/ports/UnitInputPort.java b/src/main/java/com/jsyn/ports/UnitInputPort.java new file mode 100644 index 0000000..3eda1f6 --- /dev/null +++ b/src/main/java/com/jsyn/ports/UnitInputPort.java @@ -0,0 +1,254 @@ +/* + * 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 java.io.PrintStream; + +import com.softsynth.shared.time.ScheduledCommand; +import com.softsynth.shared.time.TimeStamp; + +/** + * A port that is used to pass values into a UnitGenerator. + * + * @author Phil Burk 2009 Mobileer Inc + */ +public class UnitInputPort extends UnitBlockPort implements ConnectableInput, SettablePort { + private double minimum = 0.0; + private double maximum = 1.0; + private double defaultValue = 0.0; + private double[] setValues; + private boolean valueAdded = false; + + /** + * @param numParts typically 1, use 2 for stereo ports + * @param name name that may be used in GUIs + * @param defaultValue + */ + public UnitInputPort(int numParts, String name, double defaultValue) { + super(numParts, name, defaultValue); + setDefault(defaultValue); + setValues = new double[numParts]; + for (int i = 0; i < numParts; i++) { + setValues[i] = defaultValue; + } + } + + public UnitInputPort(String name, double defaultValue) { + this(1, name, defaultValue); + } + + public UnitInputPort(String name) { + this(1, name, 0.0); + } + + public UnitInputPort(int numParts, String name) { + this(numParts, name, 0.0); + } + + @Override + protected void makeParts(int numParts, double defaultValue) { + parts = new InputMixingBlockPart[numParts]; + for (int i = 0; i < numParts; i++) { + parts[i] = new InputMixingBlockPart(this, defaultValue); + } + } + + /** + * This is used internally by the SynthesisEngine to execute units based on their connections. + * + * @param frameCount + * @param start + * @param limit + */ + @Override + public void pullData(long frameCount, int start, int limit) { + for (PortBlockPart part : parts) { + ((InputMixingBlockPart) part).pullData(frameCount, start, limit); + } + } + + @Override + protected void setValueInternal(int partNum, double value) { + super.setValueInternal(partNum, value); + setValues[partNum] = value; + } + + public void set(double value) { + set(0, value); + } + + public void set(final int partNum, final double value) { + // Trigger exception now if out of range. + setValues[partNum] = value; + queueCommand(new ScheduledCommand() { + @Override + public void run() { + setValueInternal(partNum, value); + } + }); + } + + public void set(double value, TimeStamp time) { + set(0, value, time); + } + + public void set(double value, double time) { + set(0, value, time); + } + + public void set(int partNum, double value, double time) { + set(partNum, value, new TimeStamp(time)); + } + + @Override + public void set(final int partNum, final double value, TimeStamp timeStamp) { + // Trigger exception now if out of range. + getValue(partNum); + scheduleCommand(timeStamp, new ScheduledCommand() { + @Override + public void run() { + setValueInternal(partNum, value); + } + }); + } + + /** + * Value of a port based on the set() calls. Not affected by connected ports. + * + * @param partNum + * @return value as set + */ + @Override + public double get(int partNum) { + return setValues[partNum]; + } + + public double getMaximum() { + return maximum; + } + + /** + * The minimum and maximum are only used when setting up knobs or other control systems. The + * internal values are not clipped to this range. + * + * @param maximum + */ + public void setMaximum(double maximum) { + this.maximum = maximum; + } + + public double getMinimum() { + return minimum; + } + + public void setMinimum(double minimum) { + this.minimum = minimum; + } + + public double getDefault() { + return defaultValue; + } + + public void setDefault(double defaultValue) { + this.defaultValue = defaultValue; + } + + /** + * Convenience function for setting limits on a port. These limits are recommended values when + * setting up a GUI. It is possible to set a port to a value outside these limits. + * + * @param minimum + * @param value default value, will be clipped to min/max + * @param maximum + */ + public void setup(double minimum, double value, double maximum) { + setMinimum(minimum); + setMaximum(maximum); + setDefault(value); + set(value); + } + + // Grab min, max, default from another port. + public void setup(UnitInputPort other) { + setup(other.getMinimum(), other.getDefault(), other.getMaximum()); + } + + public boolean isValueAdded() { + return valueAdded; + } + + /** + * If set false then the set() value will be ignored when other ports are connected to this port. + * The sum of the connected port values will be used instead. + * + * If set true then the set() value will be added to the sum of the connected port values. + * This is useful when you want to modulate the set value. + * + * The default is false. + * + * @param valueAdded + */ + public void setValueAdded(boolean valueAdded) { + this.valueAdded = valueAdded; + } + + public void connect(int thisPartNum, UnitOutputPort otherPort, int otherPartNum, + TimeStamp timeStamp) { + otherPort.connect(otherPartNum, this, thisPartNum, timeStamp); + } + + /** Connect an input to an output port. */ + public void connect(int thisPartNum, UnitOutputPort otherPort, int otherPartNum) { + // Typically connections are made from output to input because it is + // more intuitive. + otherPort.connect(otherPartNum, this, thisPartNum); + } + + public void connect(UnitOutputPort otherPort) { + connect(0, otherPort, 0); + } + + @Override + public void connect(ConnectableOutput other) { + other.connect(this); + } + + public void disconnect(int thisPartNum, UnitOutputPort otherPort, int otherPartNum) { + otherPort.disconnect(otherPartNum, this, thisPartNum); + } + + @Override + public PortBlockPart getPortBlockPart() { + return parts[0]; + } + + public ConnectableInput getConnectablePart(int i) { + return parts[i]; + } + + @Override + public void disconnect(ConnectableOutput other) { + other.disconnect(this); + } + + public void printConnections(PrintStream out, int level) { + for (PortBlockPart part : parts) { + ((InputMixingBlockPart) part).printConnections(out, level); + } + } + +} diff --git a/src/main/java/com/jsyn/ports/UnitOutputPort.java b/src/main/java/com/jsyn/ports/UnitOutputPort.java new file mode 100644 index 0000000..6fcd758 --- /dev/null +++ b/src/main/java/com/jsyn/ports/UnitOutputPort.java @@ -0,0 +1,103 @@ +/* + * 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.unitgen.UnitSink; +import com.softsynth.shared.time.TimeStamp; + +/** + * Units write to their output port blocks. Other multiple connected input ports read from them. + * + * @author Phil Burk (C) 2009 Mobileer Inc + */ + +public class UnitOutputPort extends UnitBlockPort implements ConnectableOutput, GettablePort { + public UnitOutputPort() { + this("Output"); + } + + public UnitOutputPort(String name) { + this(1, name, 0.0); + } + + public UnitOutputPort(int numParts, String name) { + this(numParts, name, 0.0); + } + + public UnitOutputPort(int numParts, String name, double defaultValue) { + super(numParts, name, defaultValue); + } + + public void flatten() { + for (PortBlockPart part : parts) { + part.flatten(); + } + } + + public void connect(int thisPartNum, UnitInputPort otherPort, int otherPartNum) { + PortBlockPart source = parts[thisPartNum]; + PortBlockPart destination = otherPort.parts[otherPartNum]; + source.connect(destination); + } + + public void connect(int thisPartNum, UnitInputPort otherPort, int otherPartNum, + TimeStamp timeStamp) { + PortBlockPart source = parts[thisPartNum]; + PortBlockPart destination = otherPort.parts[otherPartNum]; + source.connect(destination, timeStamp); + } + + public void connect(UnitInputPort input) { + connect(0, input, 0); + } + + @Override + public void connect(ConnectableInput input) { + parts[0].connect(input); + } + + public void connect(UnitSink sink) { + connect(0, sink.getInput(), 0); + } + + public void disconnect(int thisPartNum, UnitInputPort otherPort, int otherPartNum) { + PortBlockPart source = parts[thisPartNum]; + PortBlockPart destination = otherPort.parts[otherPartNum]; + source.disconnect(destination); + } + + public void disconnect(int thisPartNum, UnitInputPort otherPort, int otherPartNum, + TimeStamp timeStamp) { + PortBlockPart source = parts[thisPartNum]; + PortBlockPart destination = otherPort.parts[otherPartNum]; + source.disconnect(destination, timeStamp); + } + + public void disconnect(UnitInputPort otherPort) { + disconnect(0, otherPort, 0); + } + + @Override + public void disconnect(ConnectableInput input) { + parts[0].disconnect(input); + } + + public ConnectableOutput getConnectablePart(int i) { + return parts[i]; + } + +} diff --git a/src/main/java/com/jsyn/ports/UnitPort.java b/src/main/java/com/jsyn/ports/UnitPort.java new file mode 100644 index 0000000..a652e68 --- /dev/null +++ b/src/main/java/com/jsyn/ports/UnitPort.java @@ -0,0 +1,85 @@ +/* + * 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.engine.SynthesisEngine; +import com.jsyn.unitgen.UnitGenerator; +import com.softsynth.shared.time.ScheduledCommand; +import com.softsynth.shared.time.TimeStamp; + +/** + * Basic audio port for JSyn unit generators. + * + * @author Phil Burk (C) 2009 Mobileer Inc + */ +public class UnitPort { + private String name; + private UnitGenerator unit; + + public UnitPort(String name) { + this.name = name; + } + + public void setName(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setUnitGenerator(UnitGenerator unit) { + // If a port is in a circuit then we want to just use the lower level + // unit that instantiated the circuit. + if (this.unit == null) { + this.unit = unit; + } + } + + public UnitGenerator getUnitGenerator() { + return unit; + } + + SynthesisEngine getSynthesisEngine() { + if (unit == null) { + return null; + } + return unit.getSynthesisEngine(); + } + + public int getNumParts() { + return 1; + } + + public void scheduleCommand(TimeStamp timeStamp, ScheduledCommand scheduledCommand) { + if (getSynthesisEngine() == null) { + scheduledCommand.run(); + } else { + getSynthesisEngine().scheduleCommand(timeStamp, scheduledCommand); + } + } + + public void queueCommand(ScheduledCommand scheduledCommand) { + if (getSynthesisEngine() == null) { + scheduledCommand.run(); + } else { + getSynthesisEngine().scheduleCommand(getSynthesisEngine().createTimeStamp(), + scheduledCommand); + } + } + +} diff --git a/src/main/java/com/jsyn/ports/UnitSpectralInputPort.java b/src/main/java/com/jsyn/ports/UnitSpectralInputPort.java new file mode 100644 index 0000000..bdf0ff5 --- /dev/null +++ b/src/main/java/com/jsyn/ports/UnitSpectralInputPort.java @@ -0,0 +1,83 @@ +/* + * 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.Spectrum; + +public class UnitSpectralInputPort extends UnitPort implements ConnectableInput { + private UnitSpectralOutputPort other; + + private Spectrum spectrum; + + public UnitSpectralInputPort() { + this("Output"); + } + + public UnitSpectralInputPort(String name) { + super(name); + } + + public void setSpectrum(Spectrum spectrum) { + this.spectrum = spectrum; + } + + public Spectrum getSpectrum() { + if (other == null) { + return spectrum; + } else { + return other.getSpectrum(); + } + } + + @Override + public void connect(ConnectableOutput other) { + if (other instanceof UnitSpectralOutputPort) { + this.other = (UnitSpectralOutputPort) other; + } else { + throw new RuntimeException( + "Can only connect UnitSpectralOutputPort to UnitSpectralInputPort!"); + } + } + + @Override + public void disconnect(ConnectableOutput other) { + if (this.other == other) { + this.other = null; + } + } + + @Override + public PortBlockPart getPortBlockPart() { + return null; + } + + @Override + public void pullData(long frameCount, int start, int limit) { + if (other != null) { + other.getUnitGenerator().pullData(frameCount, start, limit); + } + } + + public boolean isAvailable() { + if (other != null) { + return other.isAvailable(); + } else { + return (spectrum != null); + } + } + +} diff --git a/src/main/java/com/jsyn/ports/UnitSpectralOutputPort.java b/src/main/java/com/jsyn/ports/UnitSpectralOutputPort.java new file mode 100644 index 0000000..51633ce --- /dev/null +++ b/src/main/java/com/jsyn/ports/UnitSpectralOutputPort.java @@ -0,0 +1,69 @@ +/* + * 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.Spectrum; + +public class UnitSpectralOutputPort extends UnitPort implements ConnectableOutput { + private Spectrum spectrum; + private boolean available; + + public UnitSpectralOutputPort() { + this("Output"); + } + + public UnitSpectralOutputPort(int size) { + this("Output", size); + } + + public UnitSpectralOutputPort(String name) { + super(name); + spectrum = new Spectrum(); + } + + public UnitSpectralOutputPort(String name, int size) { + super(name); + spectrum = new Spectrum(size); + } + + public void setSize(int size) { + spectrum.setSize(size); + } + + public Spectrum getSpectrum() { + return spectrum; + } + + public void advance() { + available = true; + } + + @Override + public void connect(ConnectableInput other) { + other.connect(this); + } + + @Override + public void disconnect(ConnectableInput other) { + other.disconnect(this); + } + + public boolean isAvailable() { + return available; + } + +} diff --git a/src/main/java/com/jsyn/ports/UnitVariablePort.java b/src/main/java/com/jsyn/ports/UnitVariablePort.java new file mode 100644 index 0000000..60b64fd --- /dev/null +++ b/src/main/java/com/jsyn/ports/UnitVariablePort.java @@ -0,0 +1,64 @@ +/* + * 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.softsynth.shared.time.ScheduledCommand; +import com.softsynth.shared.time.TimeStamp; + +public class UnitVariablePort extends UnitPort implements SettablePort { + private double value; + + public UnitVariablePort(String name, double defaultValue) { + super(name); + value = defaultValue; + } + + public UnitVariablePort(String name) { + super(name); + } + + public void setValue(double value) { + this.value = value; + } + + public void set(double value) { + this.value = value; + } + + public double get() { + return value; + } + + public double getValue() { + return value; + } + + @Override + public double getValue(int partNum) { + return value; + } + + @Override + public void set(int partNum, final double value, TimeStamp timeStamp) { + scheduleCommand(timeStamp, new ScheduledCommand() { + @Override + public void run() { + set(value); + } + }); + } +} diff --git a/src/main/java/com/jsyn/ports/package.html b/src/main/java/com/jsyn/ports/package.html new file mode 100644 index 0000000..3547618 --- /dev/null +++ b/src/main/java/com/jsyn/ports/package.html @@ -0,0 +1,13 @@ + + + + +JSyn Ports + + +

Ports are used to pass audio data in and out of UnitGenerators. +They can also be used to connect UnitGenerators together so that signals can flow between them. +The UnitDataQueuePort contains a FIFO that will accept envelope and sample data. +

+ + \ No newline at end of file diff --git a/src/main/java/com/jsyn/scope/AudioScope.java b/src/main/java/com/jsyn/scope/AudioScope.java new file mode 100644 index 0000000..7b2a98c --- /dev/null +++ b/src/main/java/com/jsyn/scope/AudioScope.java @@ -0,0 +1,101 @@ +/* + * Copyright 2010 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.scope; + +import com.jsyn.Synthesizer; +import com.jsyn.ports.UnitOutputPort; +import com.jsyn.scope.swing.AudioScopeView; + +// TODO Auto and Manual triggers. +// TODO Auto scaling of vertical. +// TODO Fixed size Y scale knobs. +// TODO Pan back and forth around trigger. +// TODO Continuous capture +/** + * Digital oscilloscope for JSyn. + * + * @author Phil Burk (C) 2010 Mobileer Inc + */ +public class AudioScope { + public enum TriggerMode { + AUTO, NORMAL // , MANUAL + } + + public enum ViewMode { + WAVEFORM, SPECTRUM + } + + private AudioScopeView audioScopeView = null; + private AudioScopeModel audioScopeModel; + + public AudioScope(Synthesizer synth) { + audioScopeModel = new AudioScopeModel(synth); + } + + public AudioScopeProbe addProbe(UnitOutputPort output) { + return addProbe(output, 0); + } + + public AudioScopeProbe addProbe(UnitOutputPort output, int partIndex) { + return audioScopeModel.addProbe(output, partIndex); + } + + public void start() { + audioScopeModel.start(); + } + + public void stop() { + audioScopeModel.stop(); + } + + public AudioScopeModel getModel() { + return audioScopeModel; + } + + public AudioScopeView getView() { + if (audioScopeView == null) { + audioScopeView = new AudioScopeView(); + audioScopeView.setModel(audioScopeModel); + } + return audioScopeView; + } + + public void setTriggerMode(TriggerMode triggerMode) { + audioScopeModel.setTriggerMode(triggerMode); + } + + public void setTriggerSource(AudioScopeProbe probe) { + audioScopeModel.setTriggerSource(probe); + } + + public void setTriggerLevel(double level) { + getModel().getTriggerModel().getLevelModel().setDoubleValue(level); + } + + public double getTriggerLevel() { + return getModel().getTriggerModel().getLevelModel().getDoubleValue(); + } + + /** + * Not yet implemented. + * @param viewMode + */ + public void setViewMode(ViewMode viewMode) { + // TODO Auto-generated method stub + } + +} diff --git a/src/main/java/com/jsyn/scope/AudioScopeModel.java b/src/main/java/com/jsyn/scope/AudioScopeModel.java new file mode 100644 index 0000000..85c4413 --- /dev/null +++ b/src/main/java/com/jsyn/scope/AudioScopeModel.java @@ -0,0 +1,157 @@ +/* + * 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.scope; + +import java.util.ArrayList; +import java.util.concurrent.CopyOnWriteArrayList; + +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +import com.jsyn.Synthesizer; +import com.jsyn.ports.UnitOutputPort; +import com.jsyn.scope.AudioScope.TriggerMode; + +public class AudioScopeModel implements Runnable { + private static final int PRE_TRIGGER_SIZE = 32; + private Synthesizer synthesisEngine; + private ArrayList probes = new ArrayList(); + private CopyOnWriteArrayList changeListeners = new CopyOnWriteArrayList(); + private MultiChannelScopeProbeUnit probeUnit; + private double timeToArm; + private double period = 0.2; + private TriggerModel triggerModel; + + public AudioScopeModel(Synthesizer synth) { + this.synthesisEngine = synth; + triggerModel = new TriggerModel(); + } + + public AudioScopeProbe addProbe(UnitOutputPort output, int partIndex) { + AudioScopeProbe probe = new AudioScopeProbe(this, output, partIndex); + DefaultWaveTraceModel waveTraceModel = new DefaultWaveTraceModel(this, probes.size()); + probe.setWaveTraceModel(waveTraceModel); + probes.add(probe); + if (triggerModel.getSource() == null) { + triggerModel.setSource(probe); + } + return probe; + } + + public void start() { + stop(); + probeUnit = new MultiChannelScopeProbeUnit(probes.size(), triggerModel); + synthesisEngine.add(probeUnit); + for (int i = 0; i < probes.size(); i++) { + AudioScopeProbe probe = probes.get(i); + probe.getSource().connect(probe.getPartIndex(), probeUnit.input, i); + } + // Connect trigger signal to input of probe. + triggerModel.getSource().getSource() + .connect(triggerModel.getSource().getPartIndex(), probeUnit.trigger, 0); + probeUnit.start(); + + // Get synthesizer time in seconds. + timeToArm = synthesisEngine.getCurrentTime(); + probeUnit.arm(timeToArm, this); + } + + public void stop() { + if (probeUnit != null) { + for (int i = 0; i < probes.size(); i++) { + probeUnit.input.disconnectAll(i); + } + probeUnit.trigger.disconnectAll(); + probeUnit.stop(); + synthesisEngine.remove(probeUnit); + probeUnit = null; + } + } + + public AudioScopeProbe[] getProbes() { + return probes.toArray(new AudioScopeProbe[0]); + } + + public Synthesizer getSynthesizer() { + return synthesisEngine; + } + + @Override + public void run() { + fireChangeListeners(); + timeToArm = synthesisEngine.getCurrentTime(); + timeToArm += period; + probeUnit.arm(timeToArm, this); + } + + private void fireChangeListeners() { + ChangeEvent changeEvent = new ChangeEvent(this); + for (ChangeListener listener : changeListeners) { + listener.stateChanged(changeEvent); + } + // debug(); + } + + public void addChangeListener(ChangeListener changeListener) { + changeListeners.add(changeListener); + } + + public void removeChangeListener(ChangeListener changeListener) { + changeListeners.remove(changeListener); + } + + public void setTriggerMode(TriggerMode triggerMode) { + triggerModel.getModeModel().setSelectedItem(triggerMode); + } + + public void setTriggerSource(AudioScopeProbe probe) { + triggerModel.setSource(probe); + } + + public double getSample(int bufferIndex, int i) { + return probeUnit.getSample(bufferIndex, i); + } + + public int getFramesPerBuffer() { + return probeUnit.getFramesPerBuffer(); + } + + public int getFramesCaptured() { + return probeUnit.getFramesCaptured(); + } + + public int getVisibleSize() { + int size = 0; + if (probeUnit != null) { + size = probeUnit.getPostTriggerSize() + PRE_TRIGGER_SIZE; + if (size > getFramesCaptured()) { + size = getFramesCaptured(); + } + } + return size; + } + + public int getStartIndex() { + // TODO Add pan support here. + return getFramesCaptured() - getVisibleSize(); + } + + public TriggerModel getTriggerModel() { + return triggerModel; + } + +} diff --git a/src/main/java/com/jsyn/scope/AudioScopeProbe.java b/src/main/java/com/jsyn/scope/AudioScopeProbe.java new file mode 100644 index 0000000..f1aad65 --- /dev/null +++ b/src/main/java/com/jsyn/scope/AudioScopeProbe.java @@ -0,0 +1,94 @@ +/* + * Copyright 2010 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.scope; + +import java.awt.Color; + +import javax.swing.JToggleButton.ToggleButtonModel; + +import com.jsyn.ports.UnitOutputPort; +import com.jsyn.swing.ExponentialRangeModel; + +/** + * Collect data from the source and make it available to the scope. + * + * @author Phil Burk (C) 2010 Mobileer Inc + */ +public class AudioScopeProbe { + // private UnitOutputPort output; + private WaveTraceModel waveTraceModel; + private AudioScopeModel audioScopeModel; + private UnitOutputPort source; + private int partIndex; + private Color color; + private ExponentialRangeModel verticalScaleModel; + private ToggleButtonModel autoScaleButtonModel; + private double MIN_RANGE = 0.01; + private double MAX_RANGE = 100.0; + + public AudioScopeProbe(AudioScopeModel audioScopeModel, UnitOutputPort source, int partIndex) { + this.audioScopeModel = audioScopeModel; + this.source = source; + this.partIndex = partIndex; + + verticalScaleModel = new ExponentialRangeModel("VScale", 1000, MIN_RANGE, MAX_RANGE, + MIN_RANGE); + autoScaleButtonModel = new ToggleButtonModel(); + autoScaleButtonModel.setSelected(true); + } + + public WaveTraceModel getWaveTraceModel() { + return waveTraceModel; + } + + public void setWaveTraceModel(WaveTraceModel waveTraceModel) { + this.waveTraceModel = waveTraceModel; + } + + public UnitOutputPort getSource() { + return source; + } + + public int getPartIndex() { + return partIndex; + } + + public Color getColor() { + return color; + } + + public void setColor(Color color) { + this.color = color; + } + + public void setAutoScaleEnabled(boolean enabled) { + autoScaleButtonModel.setSelected(enabled); + } + + public void setVerticalScale(double max) { + verticalScaleModel.setDoubleValue(max); + } + + public ExponentialRangeModel getVerticalScaleModel() { + return verticalScaleModel; + } + + public ToggleButtonModel getAutoScaleButtonModel() { + return autoScaleButtonModel; + } + +} diff --git a/src/main/java/com/jsyn/scope/DefaultWaveTraceModel.java b/src/main/java/com/jsyn/scope/DefaultWaveTraceModel.java new file mode 100644 index 0000000..a123c0b --- /dev/null +++ b/src/main/java/com/jsyn/scope/DefaultWaveTraceModel.java @@ -0,0 +1,48 @@ +/* + * 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.scope; + +public class DefaultWaveTraceModel implements WaveTraceModel { + private AudioScopeModel audioScopeModel; + private int bufferIndex; + + public DefaultWaveTraceModel(AudioScopeModel audioScopeModel, int bufferIndex) { + this.audioScopeModel = audioScopeModel; + this.bufferIndex = bufferIndex; + } + + @Override + public double getSample(int i) { + return audioScopeModel.getSample(bufferIndex, i); + } + + @Override + public int getSize() { + return audioScopeModel.getFramesCaptured(); + } + + @Override + public int getStartIndex() { + return audioScopeModel.getStartIndex(); + } + + @Override + public int getVisibleSize() { + return audioScopeModel.getVisibleSize(); + } + +} diff --git a/src/main/java/com/jsyn/scope/MultiChannelScopeProbeUnit.java b/src/main/java/com/jsyn/scope/MultiChannelScopeProbeUnit.java new file mode 100644 index 0000000..59bb635 --- /dev/null +++ b/src/main/java/com/jsyn/scope/MultiChannelScopeProbeUnit.java @@ -0,0 +1,246 @@ +/* + * 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.scope; + +import com.jsyn.ports.UnitInputPort; +import com.jsyn.scope.AudioScope.TriggerMode; +import com.jsyn.unitgen.UnitGenerator; +import com.softsynth.shared.time.ScheduledCommand; + +/** + * Multi-channel scope probe with an independent trigger input. + * + * @author Phil Burk (C) 2009 Mobileer Inc + */ +public class MultiChannelScopeProbeUnit extends UnitGenerator { + // Signal that is captured. + public UnitInputPort input; + // Signal that triggers the probe. + public UnitInputPort trigger; + + // I am using ints instead of an enum for performance reasons. + private static final int STATE_IDLE = 0; + private static final int STATE_ARMED = 1; + private static final int STATE_READY = 2; + private static final int STATE_TRIGGERED = 3; + private int state = STATE_IDLE; + + private int numChannels; + private double[][] inputValues; + private static final int FRAMES_PER_BUFFER = 4096; // must be power of two + private static final int FRAMES_PER_BUFFER_MASK = FRAMES_PER_BUFFER - 1; + private Runnable callback; + + private TriggerModel triggerModel; + private int autoCountdown; + private int countdown; + private int postTriggerSize = 512; + SignalBuffer captureBuffer; + SignalBuffer displayBuffer; + + // Use double buffers. One for capture, one for display. + static class SignalBuffer { + float[][] buffers; + private int writeCursor; + private int triggerIndex; + private int framesCaptured; + + SignalBuffer(int numChannels) { + buffers = new float[numChannels][]; + for (int j = 0; j < numChannels; j++) { + buffers[j] = new float[FRAMES_PER_BUFFER]; + } + } + + void reset() { + writeCursor = 0; + triggerIndex = 0; + framesCaptured = 0; + } + + public void saveChannelValue(int j, float value) { + buffers[j][writeCursor] = value; + } + + public void markTrigger() { + triggerIndex = writeCursor; + } + + public void bumpCursor() { + writeCursor = (writeCursor + 1) & FRAMES_PER_BUFFER_MASK; + if (writeCursor >= FRAMES_PER_BUFFER) { + writeCursor = 0; + } + if (framesCaptured < FRAMES_PER_BUFFER) { + framesCaptured += 1; + } + } + + private int convertInternalToExternalIndex(int internalIndex) { + if (framesCaptured < FRAMES_PER_BUFFER) { + return internalIndex; + } else { + return (internalIndex - writeCursor) & (FRAMES_PER_BUFFER_MASK); + } + } + + private int convertExternalToInternalIndex(int externalIndex) { + if (framesCaptured < FRAMES_PER_BUFFER) { + return externalIndex; + } else { + return (externalIndex + writeCursor) & (FRAMES_PER_BUFFER_MASK); + } + } + + public int getTriggerIndex() { + return convertInternalToExternalIndex(triggerIndex); + } + + public int getFramesCaptured() { + return framesCaptured; + } + + public float getSample(int bufferIndex, int sampleIndex) { + int index = convertExternalToInternalIndex(sampleIndex); + return buffers[bufferIndex][index]; + } + } + + public MultiChannelScopeProbeUnit(int numChannels, TriggerModel triggerModel) { + this.numChannels = numChannels; + captureBuffer = new SignalBuffer(numChannels); + displayBuffer = new SignalBuffer(numChannels); + this.triggerModel = triggerModel; + addPort(trigger = new UnitInputPort(numChannels, "Trigger")); + addPort(input = new UnitInputPort(numChannels, "Input")); + inputValues = new double[numChannels][]; + } + + private synchronized void switchBuffers() { + SignalBuffer temp = captureBuffer; + captureBuffer = displayBuffer; + displayBuffer = temp; + } + + private void internalArm(Runnable callback) { + this.callback = callback; + state = STATE_ARMED; + captureBuffer.reset(); + } + + class ScheduledArm implements ScheduledCommand { + private Runnable callback; + + ScheduledArm(Runnable callback) { + this.callback = callback; + } + + @Override + public void run() { + internalArm(this.callback); + } + } + + /** Arm the probe at a future time. */ + public void arm(double time, Runnable callback) { + ScheduledArm command = new ScheduledArm(callback); + getSynthesisEngine().scheduleCommand(time, command); + } + + @Override + public void generate(int start, int limit) { + if (state != STATE_IDLE) { + TriggerMode triggerMode = triggerModel.getMode(); + double triggerLevel = triggerModel.getTriggerLevel(); + double[] triggerValues = trigger.getValues(); + + for (int j = 0; j < numChannels; j++) { + inputValues[j] = input.getValues(j); + } + + for (int i = start; i < limit; i++) { + // Capture one sample from each channel. + for (int j = 0; j < numChannels; j++) { + captureBuffer.saveChannelValue(j, (float) inputValues[j][i]); + } + captureBuffer.bumpCursor(); + + switch (state) { + case STATE_ARMED: + if (triggerValues[i] <= triggerLevel) { + state = STATE_READY; + autoCountdown = 44100; + } + break; + + case STATE_READY: { + boolean triggered = false; + if (triggerValues[i] > triggerLevel) { + triggered = true; + } else if (triggerMode.equals(TriggerMode.AUTO)) { + if (--autoCountdown == 0) { + triggered = true; + } + } + if (triggered) { + captureBuffer.markTrigger(); + state = STATE_TRIGGERED; + countdown = postTriggerSize; + } + } + break; + + case STATE_TRIGGERED: + countdown -= 1; + if (countdown <= 0) { + state = STATE_IDLE; + switchBuffers(); + fireCallback(); + } + break; + } + } + } + } + + private void fireCallback() { + if (callback != null) { + callback.run(); + } + } + + public float getSample(int bufferIndex, int sampleIndex) { + return displayBuffer.getSample(bufferIndex, sampleIndex); + } + + public int getTriggerIndex() { + return displayBuffer.getTriggerIndex(); + } + + public int getFramesCaptured() { + return displayBuffer.getFramesCaptured(); + } + + public int getFramesPerBuffer() { + return FRAMES_PER_BUFFER; + } + + public int getPostTriggerSize() { + return postTriggerSize; + } + +} diff --git a/src/main/java/com/jsyn/scope/TriggerModel.java b/src/main/java/com/jsyn/scope/TriggerModel.java new file mode 100644 index 0000000..0367d71 --- /dev/null +++ b/src/main/java/com/jsyn/scope/TriggerModel.java @@ -0,0 +1,67 @@ +/* + * 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.scope; + +import javax.swing.DefaultComboBoxModel; + +import com.jsyn.scope.AudioScope.TriggerMode; +import com.jsyn.swing.ExponentialRangeModel; + +public class TriggerModel { + private ExponentialRangeModel levelModel; + private DefaultComboBoxModel modeModel; + private AudioScopeProbe source; + + public TriggerModel() { + modeModel = new DefaultComboBoxModel(); + modeModel.addElement(TriggerMode.AUTO); + modeModel.addElement(TriggerMode.NORMAL); + levelModel = new ExponentialRangeModel("TriggerLevel", 1000, 0.01, 2.0, 0.04); + } + + public AudioScopeProbe getSource() { + return source; + } + + public void setSource(AudioScopeProbe source) { + this.source = source; + } + + public ExponentialRangeModel getLevelModel() { + return levelModel; + } + + public void setLevelModel(ExponentialRangeModel levelModel) { + this.levelModel = levelModel; + } + + public DefaultComboBoxModel getModeModel() { + return modeModel; + } + + public void setModeModel(DefaultComboBoxModel modeModel) { + this.modeModel = modeModel; + } + + public double getTriggerLevel() { + return levelModel.getDoubleValue(); + } + + public TriggerMode getMode() { + return (TriggerMode) modeModel.getSelectedItem(); + } +} diff --git a/src/main/java/com/jsyn/scope/WaveTraceModel.java b/src/main/java/com/jsyn/scope/WaveTraceModel.java new file mode 100644 index 0000000..e9d8bf9 --- /dev/null +++ b/src/main/java/com/jsyn/scope/WaveTraceModel.java @@ -0,0 +1,27 @@ +/* + * 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.scope; + +public interface WaveTraceModel { + int getSize(); + + int getVisibleSize(); + + int getStartIndex(); + + double getSample(int i); +} diff --git a/src/main/java/com/jsyn/scope/swing/AudioScopeProbeView.java b/src/main/java/com/jsyn/scope/swing/AudioScopeProbeView.java new file mode 100644 index 0000000..59526e1 --- /dev/null +++ b/src/main/java/com/jsyn/scope/swing/AudioScopeProbeView.java @@ -0,0 +1,45 @@ +/* + * Copyright 2010 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.scope.swing; + +import com.jsyn.scope.AudioScopeProbe; + +/** + * Wave display associated with a probe. + * + * @author Phil Burk (C) 2010 Mobileer Inc + */ +public class AudioScopeProbeView { + private AudioScopeProbe probeModel; + private WaveTraceView waveTrace; + + public AudioScopeProbeView(AudioScopeProbe probeModel) { + this.probeModel = probeModel; + waveTrace = new WaveTraceView(probeModel.getAutoScaleButtonModel(), + probeModel.getVerticalScaleModel()); + waveTrace.setModel(probeModel.getWaveTraceModel()); + } + + public WaveTraceView getWaveTraceView() { + return waveTrace; + } + + public AudioScopeProbe getModel() { + return probeModel; + } + +} diff --git a/src/main/java/com/jsyn/scope/swing/AudioScopeView.java b/src/main/java/com/jsyn/scope/swing/AudioScopeView.java new file mode 100644 index 0000000..ec1afa3 --- /dev/null +++ b/src/main/java/com/jsyn/scope/swing/AudioScopeView.java @@ -0,0 +1,112 @@ +/* + * 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.scope.swing; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Dimension; +import java.util.ArrayList; + +import javax.swing.JPanel; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +import com.jsyn.scope.AudioScopeModel; +import com.jsyn.scope.AudioScopeProbe; + +public class AudioScopeView extends JPanel { + private static final long serialVersionUID = -7507986850757860853L; + private AudioScopeModel audioScopeModel; + private ArrayList probeViews = new ArrayList(); + private MultipleWaveDisplay multipleWaveDisplay; + private boolean showControls = false; + private ScopeControlPanel controlPanel = null; + + public AudioScopeView() { + setBackground(Color.GREEN); + } + + public void setModel(AudioScopeModel audioScopeModel) { + this.audioScopeModel = audioScopeModel; + // Create a view for each probe. + probeViews.clear(); + for (AudioScopeProbe probeModel : audioScopeModel.getProbes()) { + AudioScopeProbeView audioScopeProbeView = new AudioScopeProbeView(probeModel); + probeViews.add(audioScopeProbeView); + } + setupGUI(); + + // Listener for signal change events. + audioScopeModel.addChangeListener(new ChangeListener() { + @Override + public void stateChanged(ChangeEvent e) { + multipleWaveDisplay.repaint(); + } + }); + + } + + private void setupGUI() { + removeAll(); + setLayout(new BorderLayout()); + multipleWaveDisplay = new MultipleWaveDisplay(); + + for (AudioScopeProbeView probeView : probeViews) { + multipleWaveDisplay.addWaveTrace(probeView.getWaveTraceView()); + probeView.getModel().setColor(probeView.getWaveTraceView().getColor()); + } + + add(multipleWaveDisplay, BorderLayout.CENTER); + + setMinimumSize(new Dimension(400, 200)); + setPreferredSize(new Dimension(600, 250)); + setMaximumSize(new Dimension(1200, 300)); + } + + /** @deprecated Use setControlsVisible() instead. */ + @Deprecated + public void setShowControls(boolean show) { + setControlsVisible(show); + } + + public void setControlsVisible(boolean show) { + if (this.showControls) { + if (!show && (controlPanel != null)) { + remove(controlPanel); + } + } else { + if (show) { + if (controlPanel == null) { + controlPanel = new ScopeControlPanel(this); + } + add(controlPanel, BorderLayout.EAST); + validate(); + } + } + + this.showControls = show; + } + + public AudioScopeModel getModel() { + return audioScopeModel; + } + + public AudioScopeProbeView[] getProbeViews() { + return probeViews.toArray(new AudioScopeProbeView[0]); + } + +} diff --git a/src/main/java/com/jsyn/scope/swing/MultipleWaveDisplay.java b/src/main/java/com/jsyn/scope/swing/MultipleWaveDisplay.java new file mode 100644 index 0000000..0259850 --- /dev/null +++ b/src/main/java/com/jsyn/scope/swing/MultipleWaveDisplay.java @@ -0,0 +1,58 @@ +/* + * Copyright 2011 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.scope.swing; + +import java.awt.Color; +import java.awt.Graphics; +import java.util.ArrayList; + +import javax.swing.JPanel; + +/** + * Display multiple waveforms together in different colors. + * + * @author Phil Burk (C) 2011 Mobileer Inc + */ +public class MultipleWaveDisplay extends JPanel { + private static final long serialVersionUID = -5157397030540800373L; + + private ArrayList waveTraceViews = new ArrayList(); + private Color[] defaultColors = { + Color.BLUE, Color.RED, Color.BLACK, Color.MAGENTA, Color.GREEN, Color.ORANGE + }; + + public MultipleWaveDisplay() { + setBackground(Color.WHITE); + } + + public void addWaveTrace(WaveTraceView waveTraceView) { + if (waveTraceView.getColor() == null) { + waveTraceView.setColor(defaultColors[waveTraceViews.size() % defaultColors.length]); + } + waveTraceViews.add(waveTraceView); + } + + @Override + public void paintComponent(Graphics g) { + super.paintComponent(g); + int width = getWidth(); + int height = getHeight(); + for (WaveTraceView waveTraceView : waveTraceViews.toArray(new WaveTraceView[0])) { + waveTraceView.drawWave(g, width, height); + } + } +} diff --git a/src/main/java/com/jsyn/scope/swing/ScopeControlPanel.java b/src/main/java/com/jsyn/scope/swing/ScopeControlPanel.java new file mode 100644 index 0000000..7f3a026 --- /dev/null +++ b/src/main/java/com/jsyn/scope/swing/ScopeControlPanel.java @@ -0,0 +1,46 @@ +/* + * 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.scope.swing; + +import java.awt.GridLayout; + +import javax.swing.JPanel; + +import com.jsyn.scope.AudioScopeModel; + +public class ScopeControlPanel extends JPanel { + private static final long serialVersionUID = 7738305116057614812L; + private AudioScopeModel audioScopeModel; + private ScopeTriggerPanel triggerPanel; + private JPanel probeRows; + + public ScopeControlPanel(AudioScopeView audioScopeView) { + setLayout(new GridLayout(0, 1)); + this.audioScopeModel = audioScopeView.getModel(); + triggerPanel = new ScopeTriggerPanel(audioScopeModel); + add(triggerPanel); + + probeRows = new JPanel(); + probeRows.setLayout(new GridLayout(1, 0)); + add(probeRows); + for (AudioScopeProbeView probeView : audioScopeView.getProbeViews()) { + ScopeProbePanel probePanel = new ScopeProbePanel(probeView); + probeRows.add(probePanel); + } + } + +} diff --git a/src/main/java/com/jsyn/scope/swing/ScopeProbePanel.java b/src/main/java/com/jsyn/scope/swing/ScopeProbePanel.java new file mode 100644 index 0000000..a0dec91 --- /dev/null +++ b/src/main/java/com/jsyn/scope/swing/ScopeProbePanel.java @@ -0,0 +1,87 @@ +/* + * 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.scope.swing; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +import javax.swing.BorderFactory; +import javax.swing.JCheckBox; +import javax.swing.JPanel; +import javax.swing.JToggleButton.ToggleButtonModel; + +import com.jsyn.scope.AudioScopeProbe; +import com.jsyn.swing.RotaryTextController; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ScopeProbePanel extends JPanel { + + private static final Logger LOGGER = LoggerFactory.getLogger(ScopeProbePanel.class); + private static final long serialVersionUID = 4511589171299298548L; + + private AudioScopeProbeView audioScopeProbeView; + private AudioScopeProbe audioScopeProbe; + private RotaryTextController verticalScaleKnob; + private JCheckBox autoBox; + private ToggleButtonModel autoScaleModel; + + public ScopeProbePanel(AudioScopeProbeView probeView) { + this.audioScopeProbeView = probeView; + setLayout(new BorderLayout()); + + setBorder(BorderFactory.createLineBorder(Color.GRAY, 3)); + + // Add a colored box to match the waveform color. + JPanel colorPanel = new JPanel(); + colorPanel.setMinimumSize(new Dimension(40, 40)); + audioScopeProbe = probeView.getModel(); + colorPanel.setBackground(audioScopeProbe.getColor()); + add(colorPanel, BorderLayout.NORTH); + + // Knob for tweaking vertical range. + verticalScaleKnob = new RotaryTextController(audioScopeProbeView.getWaveTraceView() + .getVerticalRangeModel(), 5); + add(verticalScaleKnob, BorderLayout.CENTER); + verticalScaleKnob.setTitle("YScale"); + + // Auto ranging checkbox. + autoBox = new JCheckBox("Auto"); + autoScaleModel = audioScopeProbeView.getWaveTraceView().getAutoButtonModel(); + autoScaleModel.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + ToggleButtonModel model = (ToggleButtonModel) e.getSource(); + boolean enabled = !model.isSelected(); + LOGGER.debug("Knob enabled = " + enabled); + verticalScaleKnob.setEnabled(!model.isSelected()); + } + }); + autoBox.setModel(autoScaleModel); + add(autoBox, BorderLayout.SOUTH); + + verticalScaleKnob.setEnabled(!autoScaleModel.isSelected()); + + setMinimumSize(new Dimension(80, 100)); + setPreferredSize(new Dimension(80, 150)); + setMaximumSize(new Dimension(120, 200)); + } + +} diff --git a/src/main/java/com/jsyn/scope/swing/ScopeTriggerPanel.java b/src/main/java/com/jsyn/scope/swing/ScopeTriggerPanel.java new file mode 100644 index 0000000..9c22aa1 --- /dev/null +++ b/src/main/java/com/jsyn/scope/swing/ScopeTriggerPanel.java @@ -0,0 +1,47 @@ +/* + * 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.scope.swing; + +import java.awt.BorderLayout; + +import javax.swing.DefaultComboBoxModel; +import javax.swing.JComboBox; +import javax.swing.JPanel; + +import com.jsyn.scope.AudioScopeModel; +import com.jsyn.scope.TriggerModel; +import com.jsyn.scope.AudioScope.TriggerMode; +import com.jsyn.swing.RotaryTextController; + +public class ScopeTriggerPanel extends JPanel { + private static final long serialVersionUID = 4511589171299298548L; + private JComboBox> triggerModeComboBox; + private RotaryTextController triggerLevelKnob; + + public ScopeTriggerPanel(AudioScopeModel audioScopeModel) { + setLayout(new BorderLayout()); + TriggerModel triggerModel = audioScopeModel.getTriggerModel(); + triggerModeComboBox = new JComboBox(triggerModel.getModeModel()); + add(triggerModeComboBox, BorderLayout.NORTH); + + triggerLevelKnob = new RotaryTextController(triggerModel.getLevelModel(), 5); + + add(triggerLevelKnob, BorderLayout.CENTER); + triggerLevelKnob.setTitle("Trigger Level"); + } + +} diff --git a/src/main/java/com/jsyn/scope/swing/WaveTraceView.java b/src/main/java/com/jsyn/scope/swing/WaveTraceView.java new file mode 100644 index 0000000..849a6f4 --- /dev/null +++ b/src/main/java/com/jsyn/scope/swing/WaveTraceView.java @@ -0,0 +1,122 @@ +/* + * 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.scope.swing; + +import java.awt.Color; +import java.awt.Graphics; + +import javax.swing.JToggleButton.ToggleButtonModel; + +import com.jsyn.scope.WaveTraceModel; +import com.jsyn.swing.ExponentialRangeModel; + +public class WaveTraceView { + private static final double AUTO_DECAY = 0.95; + private WaveTraceModel waveTraceModel; + private Color color; + private ExponentialRangeModel verticalScaleModel; + private ToggleButtonModel autoScaleButtonModel; + + private double xScaler; + private double yScalar; + private int centerY; + + public WaveTraceView(ToggleButtonModel autoButtonModel, ExponentialRangeModel verticalRangeModel) { + this.verticalScaleModel = verticalRangeModel; + this.autoScaleButtonModel = autoButtonModel; + } + + public Color getColor() { + return color; + } + + public void setColor(Color color) { + this.color = color; + } + + public ExponentialRangeModel getVerticalRangeModel() { + return verticalScaleModel; + } + + public ToggleButtonModel getAutoButtonModel() { + return autoScaleButtonModel; + } + + public void setModel(WaveTraceModel waveTraceModel) { + this.waveTraceModel = waveTraceModel; + } + + public int convertRealToY(double r) { + return centerY - (int) (yScalar * r); + } + + public void drawWave(Graphics g, int width, int height) { + double sampleMax = 0.0; + double sampleMin = 0.0; + g.setColor(color); + int numSamples = waveTraceModel.getVisibleSize(); + if (numSamples > 0) { + xScaler = (double) width / numSamples; + // Scale by 0.5 because it is bipolar. + yScalar = 0.5 * height / verticalScaleModel.getDoubleValue(); + centerY = height / 2; + + // Calculate position of first point. + int x1 = 0; + int offset = waveTraceModel.getStartIndex(); + double value = waveTraceModel.getSample(offset); + int y1 = convertRealToY(value); + + // Draw lines to remaining points. + for (int i = 1; i < numSamples; i++) { + int x2 = (int) (i * xScaler); + value = waveTraceModel.getSample(offset + i); + int y2 = convertRealToY(value); + g.drawLine(x1, y1, x2, y2); + x1 = x2; + y1 = y2; + // measure min and max for auto + if (value > sampleMax) { + sampleMax = value; + } else if (value < sampleMin) { + sampleMin = value; + } + } + + autoScaleRange(sampleMax); + } + } + + // Autoscale the vertical range. + private void autoScaleRange(double sampleMax) { + if (autoScaleButtonModel.isSelected()) { + double scaledMax = sampleMax * 1.1; + double current = verticalScaleModel.getDoubleValue(); + if (scaledMax > current) { + verticalScaleModel.setDoubleValue(scaledMax); + } else { + double decayed = current * AUTO_DECAY; + if (decayed > verticalScaleModel.getMinimum()) { + if (scaledMax < decayed) { + verticalScaleModel.setDoubleValue(decayed); + } + } + } + } + } + +} diff --git a/src/main/java/com/jsyn/swing/ASCIIMusicKeyboard.java b/src/main/java/com/jsyn/swing/ASCIIMusicKeyboard.java new file mode 100644 index 0000000..dc02259 --- /dev/null +++ b/src/main/java/com/jsyn/swing/ASCIIMusicKeyboard.java @@ -0,0 +1,199 @@ +/* + * Copyright 2012 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.swing; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.KeyEvent; +import java.awt.event.KeyListener; +import java.util.HashSet; + +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JLabel; +import javax.swing.JPanel; + +/** + * Support for playing musical scales on the ASCII keyboard of a computer. Has a Sustain checkbox + * that simulates a sustain pedal. Auto-repeat keys are detected and suppressed. + * + * @author Phil Burk (C) 2012 Mobileer Inc + */ +@SuppressWarnings("serial") +public abstract class ASCIIMusicKeyboard extends JPanel { + + private static final Logger LOGGER = LoggerFactory.getLogger(ASCIIMusicKeyboard.class); + + private final JCheckBox sustainBox; + private final JButton focusButton; + public static final String PENTATONIC_KEYS = "zxcvbasdfgqwert12345"; + public static final String SEPTATONIC_KEYS = "zxcvbnmasdfghjqwertyu1234567890"; + private String keyboardLayout = SEPTATONIC_KEYS; /* default music keyboard layout */ + private int basePitch = 48; + private final KeyListener keyListener; + private final JLabel countLabel; + private int onCount; + private int offCount; + private int pressedCount; + private int releasedCount; + private final HashSet pressedKeys = new HashSet(); + private final HashSet onKeys = new HashSet(); + + public ASCIIMusicKeyboard() { + focusButton = new JButton("Click here to play ASCII keys."); + focusButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent arg0) { + } + }); + keyListener = new KeyListener() { + + @Override + public void keyPressed(KeyEvent e) { + int key = e.getKeyChar(); + int idx = keyboardLayout.indexOf(key); + LOGGER.debug("keyPressed " + idx); + if (idx >= 0) { + if (!pressedKeys.contains(idx)) { + keyOn(convertIndexToPitch(idx)); + onCount++; + pressedKeys.add(idx); + onKeys.add(idx); + } + } + pressedCount++; + updateCountLabel(); + } + + @Override + public void keyReleased(KeyEvent e) { + int key = e.getKeyChar(); + int idx = keyboardLayout.indexOf(key); + LOGGER.debug("keyReleased " + idx); + if (idx >= 0) { + if (!sustainBox.isSelected()) { + noteOffInternal(idx); + onKeys.remove(idx); + } + pressedKeys.remove(idx); + } + releasedCount++; + updateCountLabel(); + } + + @Override + public void keyTyped(KeyEvent arg0) { + } + }; + focusButton.addKeyListener(keyListener); + add(focusButton); + + sustainBox = new JCheckBox("sustain"); + sustainBox.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent arg0) { + if (!sustainBox.isSelected()) { + for (Integer noteIndex : onKeys) { + noteOffInternal(noteIndex); + } + onKeys.clear(); + } + } + }); + add(sustainBox); + sustainBox.addKeyListener(keyListener); + + countLabel = new JLabel("0"); + add(countLabel); + } + + private void noteOffInternal(int idx) { + keyOff(convertIndexToPitch(idx)); + offCount++; + } + + protected void updateCountLabel() { + countLabel.setText(onCount + "/" + offCount + ", " + pressedCount + "/" + releasedCount); + } + + /** + * Convert index to a MIDI noteNumber in a major scale. Result will be offset by the basePitch. + */ + public int convertIndexToPitch(int keyIndex) { + int scale[] = { + 0, 2, 4, 5, 7, 9, 11 + }; + int octave = keyIndex / scale.length; + int idx = keyIndex % scale.length; + int pitch = (octave * 12) + scale[idx]; + return pitch + basePitch; + } + + /** + * This will be called when a key is released. It may also be called for sustaining notes when + * the Sustain check box is turned off. + * + * @param keyIndex + */ + public abstract void keyOff(int keyIndex); + + /** + * This will be called when a key is pressed. + * + * @param keyIndex + */ + public abstract void keyOn(int keyIndex); + + public String getKeyboardLayout() { + return keyboardLayout; + } + + /** + * Specify the keys that will be active for music. + * For example "qwertyui". + * If the first character in the layout is + * pressed then keyOn() will be called with 0. Default is SEPTATONIC_KEYS. + * + * @param keyboardLayout defines order of playable keys + */ + public void setKeyboardLayout(String keyboardLayout) { + this.keyboardLayout = keyboardLayout; + } + + public int getBasePitch() { + return basePitch; + } + + /** + * Define offset used by convertIndexToPitch(). + * + * @param basePitch + */ + public void setBasePitch(int basePitch) { + this.basePitch = basePitch; + } + + /** + * @return + */ + public KeyListener getKeyListener() { + return keyListener; + } +} diff --git a/src/main/java/com/jsyn/swing/DoubleBoundedRangeModel.java b/src/main/java/com/jsyn/swing/DoubleBoundedRangeModel.java new file mode 100644 index 0000000..647e8da --- /dev/null +++ b/src/main/java/com/jsyn/swing/DoubleBoundedRangeModel.java @@ -0,0 +1,86 @@ +/* + * Copyright 2002 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.swing; + +import javax.swing.DefaultBoundedRangeModel; + +/** + * Double precision data model for sliders and knobs. Maps integer range info to a double value. + * + * @author Phil Burk, (C) 2002 SoftSynth.com, PROPRIETARY and CONFIDENTIAL + */ +public class DoubleBoundedRangeModel extends DefaultBoundedRangeModel { + private static final long serialVersionUID = 284361767102120148L; + protected String name; + private double dmin; + private double dmax; + + public DoubleBoundedRangeModel(String name, int resolution, double dmin, double dmax, + double dval) { + this.name = name; + this.dmin = dmin; + this.dmax = dmax; + setMinimum(0); + setMaximum(resolution); + setDoubleValue(dval); + } + + public boolean equivalentTo(Object other) { + if (!(other instanceof DoubleBoundedRangeModel)) + return false; + DoubleBoundedRangeModel otherModel = (DoubleBoundedRangeModel) other; + return (getValue() == otherModel.getValue()); + } + + /** Set name of value. This may be used in labels or when saving the value. */ + public void setName(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public double getDoubleMinimum() { + return dmin; + } + + public double getDoubleMaximum() { + return dmax; + } + + public double sliderToDouble(int sliderValue) { + double doubleMin = getDoubleMinimum(); + return doubleMin + ((getDoubleMaximum() - doubleMin) * sliderValue / getMaximum()); + } + + public int doubleToSlider(double dval) { + double doubleMin = getDoubleMinimum(); + // TODO consider using Math.floor() instead of (int) if not too slow. + return (int) Math.round(getMaximum() * (dval - doubleMin) + / (getDoubleMaximum() - doubleMin)); + } + + public double getDoubleValue() { + return sliderToDouble(getValue()); + } + + public void setDoubleValue(double dval) { + setValue(doubleToSlider(dval)); + } + +} diff --git a/src/main/java/com/jsyn/swing/DoubleBoundedRangeSlider.java b/src/main/java/com/jsyn/swing/DoubleBoundedRangeSlider.java new file mode 100644 index 0000000..81b67df --- /dev/null +++ b/src/main/java/com/jsyn/swing/DoubleBoundedRangeSlider.java @@ -0,0 +1,101 @@ +/* + * Copyright 2002 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.swing; + +import java.util.Hashtable; + +import javax.swing.BorderFactory; +import javax.swing.JLabel; +import javax.swing.JSlider; +import javax.swing.border.TitledBorder; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +import com.jsyn.util.NumericOutput; + +/** + * Slider that takes a DoubleBoundedRangeModel. It displays the current value in a titled border. + * + * @author Phil Burk, (C) 2002 SoftSynth.com, PROPRIETARY and CONFIDENTIAL + */ + +public class DoubleBoundedRangeSlider extends JSlider { + /** + * + */ + private static final long serialVersionUID = -440390322602838998L; + /** Places after decimal point for display. */ + private int places; + + public DoubleBoundedRangeSlider(DoubleBoundedRangeModel model) { + this(model, 5); + } + + public DoubleBoundedRangeSlider(DoubleBoundedRangeModel model, int places) { + super(model); + this.places = places; + setBorder(BorderFactory.createTitledBorder(generateTitleText())); + model.addChangeListener(new ChangeListener() { + @Override + public void stateChanged(ChangeEvent e) { + updateTitle(); + } + }); + } + + protected void updateTitle() { + TitledBorder border = (TitledBorder) getBorder(); + if (border != null) { + border.setTitle(generateTitleText()); + repaint(); + } + } + + String generateTitleText() { + DoubleBoundedRangeModel model = (DoubleBoundedRangeModel) getModel(); + double val = model.getDoubleValue(); + String valText = NumericOutput.doubleToString(val, 0, places); + return model.getName() + " = " + valText; + } + + public void makeStandardLabels(int labelSpacing) { + setMajorTickSpacing(labelSpacing / 2); + setLabelTable(createStandardLabels(labelSpacing)); + setPaintTicks(true); + setPaintLabels(true); + } + + public double nextLabelValue(double current, double delta) { + return current + delta; + } + + public void makeLabels(double start, double delta, int places) { + DoubleBoundedRangeModel model = (DoubleBoundedRangeModel) getModel(); + // Create the label table + Hashtable labelTable = new Hashtable(); + double dval = start; + while (dval <= model.getDoubleMaximum()) { + int sliderValue = model.doubleToSlider(dval); + String text = NumericOutput.doubleToString(dval, 0, places); + labelTable.put(sliderValue, new JLabel(text)); + dval = nextLabelValue(dval, delta); + } + setLabelTable(labelTable); + setPaintLabels(true); + } + +} diff --git a/src/main/java/com/jsyn/swing/DoubleBoundedTextField.java b/src/main/java/com/jsyn/swing/DoubleBoundedTextField.java new file mode 100644 index 0000000..3301bb1 --- /dev/null +++ b/src/main/java/com/jsyn/swing/DoubleBoundedTextField.java @@ -0,0 +1,94 @@ +/* + * Copyright 2000 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.swing; + +import java.awt.Color; +import java.awt.event.KeyAdapter; +import java.awt.event.KeyEvent; + +import javax.swing.JTextField; +import javax.swing.SwingConstants; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +/** + * TextField that turns pink when modified, and white when the value is entered. + * + * @author (C) 2000-2010 Phil Burk, Mobileer Inc + * @version 16 + */ + +public class DoubleBoundedTextField extends JTextField { + private static final long serialVersionUID = 6882779668177620812L; + boolean modified = false; + int numCharacters; + private DoubleBoundedRangeModel model; + + public DoubleBoundedTextField(DoubleBoundedRangeModel pModel, int numCharacters) { + super(numCharacters); + this.model = pModel; + this.numCharacters = numCharacters; + setHorizontalAlignment(SwingConstants.LEADING); + setValue(model.getDoubleValue()); + addKeyListener(new KeyAdapter() { + @Override + public void keyTyped(KeyEvent e) { + if (e.getKeyChar() == '\n') { + model.setDoubleValue(getValue()); + } else { + markDirty(); + } + } + }); + model.addChangeListener(new ChangeListener() { + @Override + public void stateChanged(ChangeEvent e) { + setValue(model.getDoubleValue()); + } + }); + } + + private void markDirty() { + modified = true; + setBackground(Color.pink); + repaint(); + } + + private void markClean() { + modified = false; + setBackground(Color.white); + setCaretPosition(0); + repaint(); + } + + @Override + public void setText(String text) { + markDirty(); + super.setText(text); + } + + private double getValue() throws NumberFormatException { + double val = Double.valueOf(getText()).doubleValue(); + markClean(); + return val; + } + + private void setValue(double value) { + super.setText(String.format("%6.4f", value)); + markClean(); + } +} diff --git a/src/main/java/com/jsyn/swing/EnvelopeEditorBox.java b/src/main/java/com/jsyn/swing/EnvelopeEditorBox.java new file mode 100644 index 0000000..2db4c29 --- /dev/null +++ b/src/main/java/com/jsyn/swing/EnvelopeEditorBox.java @@ -0,0 +1,573 @@ +/* + * Copyright 1997 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.swing; + +import java.awt.Color; +import java.awt.Graphics; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; +import java.awt.event.MouseMotionListener; +import java.util.ArrayList; + +import com.jsyn.data.SegmentedEnvelope; +import com.jsyn.unitgen.VariableRateDataReader; + +/** + * Edit a list of ordered duration,value pairs suitable for use with a SegmentedEnvelope. + * + * @author (C) 1997-2013 Phil Burk, SoftSynth.com + * @see EnvelopePoints + * @see SegmentedEnvelope + * @see VariableRateDataReader + */ + +/* ========================================================================== */ +public class EnvelopeEditorBox extends XYController implements MouseListener, MouseMotionListener { + EnvelopePoints points; + ArrayList listeners = new ArrayList(); + int dragIndex = -1; + double dragLowLimit; + double dragHighLimit; + double draggedPoint[]; + double xBefore; // WX value before point + double xPicked; // WX value of picked point + double dragWX; + double dragWY; + int maxPoints = Integer.MAX_VALUE; + int radius = 4; + double verticalBarSpacing = 1.0; + boolean verticalBarsEnabled = false; + double maximumXRange = Double.MAX_VALUE; + double minimumXRange = 0.1; + int rangeStart = -1; // gx coordinates + int rangeEnd = -1; + int mode = EDIT_POINTS; + public final static int EDIT_POINTS = 0; + public final static int SELECT_SUSTAIN = 1; + public final static int SELECT_RELEASE = 2; + + Color rangeColor = Color.RED; + Color sustainColor = Color.BLUE; + Color releaseColor = Color.YELLOW; + Color overlapColor = Color.GREEN; + Color firstLineColor = Color.GRAY; + + public interface EditListener { + public void objectEdited(Object editor, Object edited); + } + + public EnvelopeEditorBox() { + addMouseListener(this); + addMouseMotionListener(this); + } + + public void setMaximumXRange(double maxXRange) { + maximumXRange = maxXRange; + } + + public double getMaximumXRange() { + return maximumXRange; + } + + public void setMinimumXRange(double minXRange) { + minimumXRange = minXRange; + } + + public double getMinimumXRange() { + return minimumXRange; + } + + public void setSelection(int start, int end) { + switch (mode) { + case SELECT_SUSTAIN: + points.setSustainLoop(start, end); + break; + case SELECT_RELEASE: + points.setReleaseLoop(start, end); + break; + } + // LOGGER.debug("start = " + start + ", end = " + end ); + } + + /** Set mode to either EDIT_POINTS or SELECT_SUSTAIN, SELECT_RELEASE; */ + public void setMode(int mode) { + this.mode = mode; + } + + public int getMode() { + return mode; + } + + /** + * Add a listener to receive edit events. Listener will be passed the editor object and the + * edited object. + */ + public void addEditListener(EditListener listener) { + listeners.add(listener); + } + + public void removeEditListener(EditListener listener) { + listeners.remove(listener); + } + + /** Send event to every subscribed listener. */ + public void fireObjectEdited() { + for (EditListener listener : listeners) { + listener.objectEdited(this, points); + } + } + + public void setMaxPoints(int maxPoints) { + this.maxPoints = maxPoints; + } + + public int getMaxPoints() { + return maxPoints; + } + + public int getNumPoints() { + return points.size(); + } + + public void setPoints(EnvelopePoints points) { + this.points = points; + setMaxWorldY(points.getMaximumValue()); + } + + public EnvelopePoints getPoints() { + return points; + } + + /** + * Return index of point before this X position. + */ + private int findPointBefore(double wx) { + int pnt = -1; + double px = 0.0; + xBefore = 0.0; + for (int i = 0; i < points.size(); i++) { + px += points.getDuration(i); + if (px > wx) + break; + pnt = i; + xBefore = px; + } + return pnt; + } + + private int pickPoint(double wx, double wxAperture, double wy, double wyAperture) { + double px = 0.0; + double wxLow = wx - wxAperture; + double wxHigh = wx + wxAperture; + // LOGGER.debug("wxLow = " + wxLow + ", wxHigh = " + wxHigh ); + double wyLow = wy - wyAperture; + double wyHigh = wy + wyAperture; + // LOGGER.debug("wyLow = " + wyLow + ", wyHigh = " + wyHigh ); + double wxScale = 1.0 / wxAperture; // only divide once, then multiply + double wyScale = 1.0 / wyAperture; + int bestPoint = -1; + double bestDistance = Double.MAX_VALUE; + for (int i = 0; i < points.size(); i++) { + double dar[] = points.getPoint(i); + px += dar[0]; + double py = dar[1]; + // LOGGER.debug("px = " + px + ", py = " + py ); + if ((px > wxLow) && (px < wxHigh) && (py > wyLow) && (py < wyHigh)) { + /* Inside pick range. Calculate distance squared. */ + double ndx = (px - wx) * wxScale; + double ndy = (py - wy) * wyScale; + double dist = (ndx * ndx) + (ndy * ndy); + // LOGGER.debug("dist = " + dist ); + if (dist < bestDistance) { + bestPoint = i; + bestDistance = dist; + xPicked = px; + } + } + } + return bestPoint; + } + + private void clickDownRange(boolean shiftDown, int gx, int gy) { + setSelection(-1, -1); + rangeStart = rangeEnd = gx; + repaint(); + } + + private void dragRange(int gx, int gy) { + rangeEnd = gx; + repaint(); + } + + private void clickUpRange(int gx, int gy) { + dragRange(gx, gy); + if (rangeEnd < rangeStart) { + int temp = rangeEnd; + rangeEnd = rangeStart; + rangeStart = temp; + } + // LOGGER.debug("clickUpRange: gx = " + gx + ", rangeStart = " + + // rangeStart ); + double wx = convertGXtoWX(rangeStart); + int i0 = findPointBefore(wx); + wx = convertGXtoWX(rangeEnd); + int i1 = findPointBefore(wx); + + if (i1 == i0) { + // set single point at zero so there is nothing played for queueOn() + if (gx < 0) { + setSelection(0, 0); + } + // else clear any existing loop + } else if (i1 == (i0 + 1)) { + setSelection(i1 + 1, i1 + 1); // set to a single point + } else if (i1 > (i0 + 1)) { + setSelection(i0 + 1, i1 + 1); // set to a range of two or more + } + + rangeStart = -1; + rangeEnd = -1; + fireObjectEdited(); + } + + private void clickDownPoints(boolean shiftDown, int gx, int gy) { + dragIndex = -1; + double wx = convertGXtoWX(gx); + double wy = convertGYtoWY(gy); + // calculate world values for aperture + double wxAp = convertGXtoWX(radius + 2) - convertGXtoWX(0); + // LOGGER.debug("wxAp = " + wxAp ); + double wyAp = convertGYtoWY(0) - convertGYtoWY(radius + 2); + // LOGGER.debug("wyAp = " + wyAp ); + int pnt = pickPoint(wx, wxAp, wy, wyAp); + // LOGGER.debug("pickPoint = " + pnt); + if (shiftDown) { + if (pnt >= 0) { + points.removePoint(pnt); + repaint(); + } + } else { + if (pnt < 0) // didn't hit one so look for point to left of click + { + if (points.size() < maxPoints) // add if room + { + pnt = findPointBefore(wx); + // LOGGER.debug("pointBefore = " + pnt); + dragIndex = pnt + 1; + if (pnt == (points.size() - 1)) { + points.add(wx - xBefore, wy); + } else { + points.insert(dragIndex, wx - xBefore, wy); + } + dragLowLimit = xBefore; + dragHighLimit = wx + (maximumXRange - points.getTotalDuration()); + repaint(); + } + } else + // hit one so drag it + { + dragIndex = pnt; + if (dragIndex <= 0) + dragLowLimit = 0.0; // FIXME envelope drag limit + else + dragLowLimit = xPicked - points.getPoint(dragIndex)[0]; + dragHighLimit = xPicked + (maximumXRange - points.getTotalDuration()); + // LOGGER.debug("dragLowLimit = " + dragLowLimit ); + } + } + // Set up drag point if we are dragging. + if (dragIndex >= 0) { + draggedPoint = points.getPoint(dragIndex); + } + + } + + private void dragPoint(int gx, int gy) { + if (dragIndex < 0) + return; + + double wx = convertGXtoWX(gx); + if (wx < dragLowLimit) + wx = dragLowLimit; + else if (wx > dragHighLimit) + wx = dragHighLimit; + draggedPoint[0] = wx - dragLowLimit; // duration + + double wy = convertGYtoWY(gy); + wy = clipWorldY(wy); + draggedPoint[1] = wy; + dragWY = wy; + dragWX = wx; + points.setDirty(true); + repaint(); + } + + private void clickUpPoints(int gx, int gy) { + dragPoint(gx, gy); + fireObjectEdited(); + dragIndex = -1; + } + + // Implement the MouseMotionListener interface for AWT 1.1 + @Override + public void mouseDragged(MouseEvent e) { + int x = e.getX(); + int y = e.getY(); + if (points == null) + return; + if (mode == EDIT_POINTS) { + dragPoint(x, y); + } else { + dragRange(x, y); + } + } + + @Override + public void mouseMoved(MouseEvent e) { + } + + // Implement the MouseListener interface for AWT 1.1 + @Override + public void mousePressed(MouseEvent e) { + int x = e.getX(); + int y = e.getY(); + if (points == null) + return; + if (mode == EDIT_POINTS) { + clickDownPoints(e.isShiftDown(), x, y); + } else { + clickDownRange(e.isShiftDown(), x, y); + } + } + + @Override + public void mouseClicked(MouseEvent e) { + } + + @Override + public void mouseReleased(MouseEvent e) { + int x = e.getX(); + int y = e.getY(); + if (points == null) + return; + if (mode == EDIT_POINTS) { + clickUpPoints(x, y); + } else { + clickUpRange(x, y); + } + } + + @Override + public void mouseEntered(MouseEvent e) { + } + + @Override + public void mouseExited(MouseEvent e) { + } + + /** + * Draw selected range. + */ + private void drawRange(Graphics g) { + if (rangeStart >= 0) { + int height = getHeight(); + int gx0 = 0, gx1 = 0; + + if (rangeEnd < rangeStart) { + gx0 = rangeEnd; + gx1 = rangeStart; + } else { + gx0 = rangeStart; + gx1 = rangeEnd; + } + g.setColor(rangeColor); + g.fillRect(gx0, 0, gx1 - gx0, height); + } + } + + private void drawUnderSelection(Graphics g, int start, int end) { + if (start >= 0) { + int height = getHeight(); + int gx0 = 0, gx1 = radius; + double wx = 0.0; + for (int i = 0; i <= (end - 1); i++) { + double dar[] = (double[]) points.elementAt(i); + wx += dar[0]; + if (start == (i + 1)) { + gx0 = convertWXtoGX(wx) + radius; + } + if (end == (i + 1)) { + gx1 = convertWXtoGX(wx) + radius; + } + } + if (gx0 == gx1) + gx0 = gx0 - radius; + g.fillRect(gx0, 0, gx1 - gx0, height); + } + } + + private void drawSelections(Graphics g) { + int sus0 = points.getSustainBegin(); + int sus1 = points.getSustainEnd(); + int rel0 = points.getReleaseBegin(); + int rel1 = points.getReleaseEnd(); + + g.setColor(sustainColor); + drawUnderSelection(g, sus0, sus1); + g.setColor(releaseColor); + drawUnderSelection(g, rel0, rel1); + // draw overlapping sustain and release region + if (sus1 >= rel0) { + int sel1 = (rel1 < sus1) ? rel1 : sus1; + g.setColor(overlapColor); + drawUnderSelection(g, rel0, sel1); + } + } + + /** + * Override this to draw a grid or other stuff under the envelope. + */ + public void drawUnderlay(Graphics g) { + if (dragIndex < 0) { + drawSelections(g); + drawRange(g); + } + if (verticalBarsEnabled) + drawVerticalBars(g); + } + + public void setVerticalBarsEnabled(boolean flag) { + verticalBarsEnabled = flag; + } + + public boolean areVerticalBarsEnabled() { + return verticalBarsEnabled; + } + + /** + * Set spacing in world coordinates. + */ + public void setVerticalBarSpacing(double spacing) { + verticalBarSpacing = spacing; + } + + public double getVerticalBarSpacing() { + return verticalBarSpacing; + } + + /** + * Draw vertical lines. + */ + private void drawVerticalBars(Graphics g) { + int width = getWidth(); + int height = getHeight(); + double wx = verticalBarSpacing; + int gx; + + // g.setColor( getBackground().darker() ); + g.setColor(Color.lightGray); + while (true) { + gx = convertWXtoGX(wx); + if (gx > width) + break; + g.drawLine(gx, 0, gx, height); + wx += verticalBarSpacing; + } + } + + public void drawPoints(Graphics g, Color lineColor) { + double wx = 0.0; + int gx1 = 0; + int gy1 = getHeight(); + for (int i = 0; i < points.size(); i++) { + double dar[] = (double[]) points.elementAt(i); + wx += dar[0]; + double wy = dar[1]; + int gx2 = convertWXtoGX(wx); + int gy2 = convertWYtoGY(wy); + if (i == 0) { + g.setColor(isEnabled() ? firstLineColor : firstLineColor.darker()); + g.drawLine(gx1, gy1, gx2, gy2); + g.setColor(isEnabled() ? lineColor : lineColor.darker()); + } else if (i > 0) { + g.drawLine(gx1, gy1, gx2, gy2); + } + int diameter = (2 * radius) + 1; + g.fillOval(gx2 - radius, gy2 - radius, diameter, diameter); + gx1 = gx2; + gy1 = gy2; + } + } + + public void drawAllPoints(Graphics g) { + drawPoints(g, getForeground()); + } + + /* Override default paint action. */ + @Override + public void paint(Graphics g) { + double wx = 0.0; + int width = getWidth(); + int height = getHeight(); + + // draw background and erase all values + g.setColor(isEnabled() ? getBackground() : getBackground().darker()); + g.fillRect(0, 0, width, height); + + if (points == null) { + g.setColor(getForeground()); + g.drawString("No EnvelopePoints", 10, 30); + return; + } + + // Determine total duration. + if (points.size() > 0) { + wx = points.getTotalDuration(); + // Adjust max X so that we see entire circle of last point. + double radiusWX = this.convertGXtoWX(radius) - this.getMinWorldX(); + double wxFar = wx + radiusWX; + if (wxFar > getMaxWorldX()) { + if (wx > maximumXRange) + wxFar = maximumXRange; + setMaxWorldX(wxFar); + } else if (wx < (getMaxWorldX() * 0.7)) { + double newMax = wx / 0.7001; // make slightly larger to prevent + // endless jitter, FIXME - still + // needed after repaint() + // removed from setMaxWorldX? + // LOGGER.debug("newMax = " + newMax ); + if (newMax < minimumXRange) + newMax = minimumXRange; + setMaxWorldX(newMax); + } + } + // LOGGER.debug("total X = " + wx ); + + drawUnderlay(g); + + drawAllPoints(g); + + /* Show X,Y,TotalX as text. */ + g.drawString(points.getName() + ", len=" + String.format("%7.3f", wx), 5, 15); + if ((draggedPoint != null) && (dragIndex >= 0)) { + String s = "i=" + dragIndex + ", dur=" + + String.format("%7.3f", draggedPoint[0]) + ", y = " + + String.format("%8.4f", draggedPoint[1]); + g.drawString(s, 5, 30); + } + } +} diff --git a/src/main/java/com/jsyn/swing/EnvelopeEditorPanel.java b/src/main/java/com/jsyn/swing/EnvelopeEditorPanel.java new file mode 100644 index 0000000..dc9f2cd --- /dev/null +++ b/src/main/java/com/jsyn/swing/EnvelopeEditorPanel.java @@ -0,0 +1,164 @@ +/* + * 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.swing; + +import java.awt.BorderLayout; +import java.awt.Button; +import java.awt.Checkbox; +import java.awt.CheckboxGroup; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Label; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; + +import javax.swing.JPanel; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +public class EnvelopeEditorPanel extends JPanel { + EnvelopeEditorBox editor; + Checkbox pointsBox; + Checkbox sustainBox; + Checkbox releaseBox; + Checkbox autoBox; + Button onButton; + Button offButton; + Button clearButton; + Button yUpButton; + Button yDownButton; + DoubleBoundedTextField zoomField; + + public EnvelopeEditorPanel(EnvelopePoints points, int maxFrames) { + setSize(600, 300); + + setLayout(new BorderLayout()); + editor = new EnvelopeEditorBox(); + editor.setMaxPoints(maxFrames); + editor.setBackground(Color.cyan); + editor.setPoints(points); + editor.setMinimumSize(new Dimension(500, 300)); + + add(editor, "Center"); + + JPanel buttonPanel = new JPanel(); + add(buttonPanel, "South"); + + CheckboxGroup cbg = new CheckboxGroup(); + pointsBox = new Checkbox("points", cbg, true); + pointsBox.addItemListener(new ItemListener() { + @Override + public void itemStateChanged(ItemEvent e) { + editor.setMode(EnvelopeEditorBox.EDIT_POINTS); + } + }); + buttonPanel.add(pointsBox); + + sustainBox = new Checkbox("onLoop", cbg, false); + sustainBox.addItemListener(new ItemListener() { + @Override + public void itemStateChanged(ItemEvent e) { + editor.setMode(EnvelopeEditorBox.SELECT_SUSTAIN); + } + }); + buttonPanel.add(sustainBox); + + releaseBox = new Checkbox("offLoop", cbg, false); + releaseBox.addItemListener(new ItemListener() { + @Override + public void itemStateChanged(ItemEvent e) { + editor.setMode(EnvelopeEditorBox.SELECT_RELEASE); + } + }); + buttonPanel.add(releaseBox); + + autoBox = new Checkbox("AutoStop", false); + /* + * buttonPanel.add( onButton = new Button( "On" ) ); onButton.addActionListener( module ); + * buttonPanel.add( offButton = new Button( "Off" ) ); offButton.addActionListener( module + * ); buttonPanel.add( clearButton = new Button( "Clear" ) ); clearButton.addActionListener( + * module ); + */ + buttonPanel.add(yUpButton = new Button("Y*2")); + yUpButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + scaleEnvelopeValues(2.0); + } + }); + + buttonPanel.add(yDownButton = new Button("Y/2")); + yDownButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + scaleEnvelopeValues(0.5); + } + }); + + /* Add a TextField for setting the Y scale. */ + double max = getMaxEnvelopeValue(editor.getPoints()); + editor.setMaxWorldY(max); + buttonPanel.add(new Label("YMax =")); + final DoubleBoundedRangeModel model = new DoubleBoundedRangeModel("YMax", 100000, 1.0, + 100001.0, 1.0); + buttonPanel.add(zoomField = new DoubleBoundedTextField(model, 8)); + model.addChangeListener(new ChangeListener() { + @Override + public void stateChanged(ChangeEvent e) { + try { + double val = model.getDoubleValue(); + editor.setMaxWorldY(val); + editor.repaint(); + } catch (NumberFormatException exp) { + zoomField.setText("ERROR"); + zoomField.selectAll(); + } + } + }); + + validate(); + } + + /** + * Multiply all the values in the envelope by scalar. + */ + double getMaxEnvelopeValue(EnvelopePoints points) { + double max = 1.0; + for (int i = 0; i < points.size(); i++) { + double value = points.getValue(i); + if (value > max) { + max = value; + } + } + return max; + } + + /** + * Multiply all the values in the envelope by scalar. + */ + void scaleEnvelopeValues(double scalar) { + EnvelopePoints points = editor.getPoints(); + for (int i = 0; i < points.size(); i++) { + double[] dar = points.getPoint(i); + dar[1] = dar[1] * scalar; // scale value + } + points.setDirty(true); + editor.repaint(); + } +} diff --git a/src/main/java/com/jsyn/swing/EnvelopePoints.java b/src/main/java/com/jsyn/swing/EnvelopePoints.java new file mode 100644 index 0000000..ab4ed03 --- /dev/null +++ b/src/main/java/com/jsyn/swing/EnvelopePoints.java @@ -0,0 +1,234 @@ +/* + * Copyright 1997 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.swing; + +import java.util.Vector; + +import com.jsyn.data.SegmentedEnvelope; + +/** + * Vector that contains duration,value pairs. Used by EnvelopeEditor + * + * @author (C) 1997 Phil Burk, SoftSynth.com + */ + +/* ========================================================================== */ +public class EnvelopePoints extends Vector { + private String name = ""; + private double maximumValue = 1.0; + private int sustainBegin = -1; + private int sustainEnd = -1; + private int releaseBegin = -1; + private int releaseEnd = -1; + private boolean dirty = false; + + /** + * Update only if points or loops were modified. + */ + public void updateEnvelopeIfDirty(SegmentedEnvelope envelope) { + if (dirty) { + updateEnvelope(envelope); + } + } + + /** + * The editor works on a vector of points, not a real envelope. The data must be written to a + * real SynthEnvelope in order to use it. + */ + public void updateEnvelope(SegmentedEnvelope envelope) { + int numFrames = size(); + for (int i = 0; i < numFrames; i++) { + envelope.write(i, getPoint(i), 0, 1); + } + envelope.setSustainBegin(getSustainBegin()); + envelope.setSustainEnd(getSustainEnd()); + envelope.setReleaseBegin(getReleaseBegin()); + envelope.setReleaseEnd(getReleaseEnd()); + envelope.setNumFrames(numFrames); + dirty = false; + } + + public void setName(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setMaximumValue(double maximumValue) { + this.maximumValue = maximumValue; + } + + public double getMaximumValue() { + return maximumValue; + } + + public void add(double dur, double value) { + double dar[] = { + dur, value + }; + addElement(dar); + dirty = true; + } + + /** + * Insert point without changing total duration by reducing next points duration. + */ + public void insert(int index, double dur, double y) { + double dar[] = { + dur, y + }; + if (index < size()) { + ((double[]) elementAt(index))[0] -= dur; + } + insertElementAt(dar, index); + + if (index <= sustainBegin) + sustainBegin += 1; + if (index <= sustainEnd) + sustainEnd += 1; + if (index <= releaseBegin) + releaseBegin += 1; + if (index <= releaseEnd) + releaseEnd += 1; + dirty = true; + } + + /** + * Remove indexed point and update sustain and release loops if necessary. Did not name this + * "remove()" because of conflicts with new JDK 1.3 method with the same name. + */ + public void removePoint(int index) { + super.removeElementAt(index); + // move down loop if points below or inside loop removed + if (index < sustainBegin) + sustainBegin -= 1; + if (index <= sustainEnd) + sustainEnd -= 1; + if (index < releaseBegin) + releaseBegin -= 1; + if (index <= releaseEnd) + releaseEnd -= 1; + + // was entire loop removed? + if (sustainBegin > sustainEnd) { + sustainBegin = -1; + sustainEnd = -1; + } + // was entire loop removed? + if (releaseBegin > releaseEnd) { + releaseBegin = -1; + releaseEnd = -1; + } + dirty = true; + } + + public double getDuration(int index) { + return ((double[]) elementAt(index))[0]; + } + + public double getValue(int index) { + return ((double[]) elementAt(index))[1]; + } + + public double[] getPoint(int index) { + return (double[]) elementAt(index); + } + + public double getTotalDuration() { + double sum = 0.0; + for (int i = 0; i < size(); i++) { + double dar[] = (double[]) elementAt(i); + sum += dar[0]; + } + return sum; + } + + /** + * Set location of Sustain Loop in units of Frames. Set SustainBegin to -1 if no Sustain Loop. + * SustainEnd value is the frame index of the frame just past the end of the loop. The number of + * frames included in the loop is (SustainEnd - SustainBegin). + */ + public void setSustainLoop(int startFrame, int endFrame) { + this.sustainBegin = startFrame; + this.sustainEnd = endFrame; + dirty = true; + } + + /*** + * @return Beginning of sustain loop or -1 if no loop. + */ + public int getSustainBegin() { + return this.sustainBegin; + } + + /*** + * @return End of sustain loop or -1 if no loop. + */ + public int getSustainEnd() { + return this.sustainEnd; + } + + /*** + * @return Size of sustain loop in frames, 0 if no loop. + */ + public int getSustainSize() { + return (this.sustainEnd - this.sustainBegin); + } + + /** + * Set location of Release Loop in units of Frames. Set ReleaseBegin to -1 if no ReleaseLoop. + * ReleaseEnd value is the frame index of the frame just past the end of the loop. The number of + * frames included in the loop is (ReleaseEnd - ReleaseBegin). + */ + public void setReleaseLoop(int startFrame, int endFrame) { + this.releaseBegin = startFrame; + this.releaseEnd = endFrame; + dirty = true; + } + + /*** + * @return Beginning of release loop or -1 if no loop. + */ + public int getReleaseBegin() { + return this.releaseBegin; + } + + /*** + * @return End of release loop or -1 if no loop. + */ + public int getReleaseEnd() { + return this.releaseEnd; + } + + /*** + * @return Size of release loop in frames, 0 if no loop. + */ + public int getReleaseSize() { + return (this.releaseEnd - this.releaseBegin); + } + + public boolean isDirty() { + return dirty; + } + + public void setDirty(boolean b) { + dirty = b; + } + +} diff --git a/src/main/java/com/jsyn/swing/ExponentialRangeModel.java b/src/main/java/com/jsyn/swing/ExponentialRangeModel.java new file mode 100644 index 0000000..c807000 --- /dev/null +++ b/src/main/java/com/jsyn/swing/ExponentialRangeModel.java @@ -0,0 +1,110 @@ +/* + * Copyright 2011 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.swing; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Maps integer range info to a double value along an exponential scale. + * + *
+ *
+ *   x = ival / resolution
+ *   f(x) = a*(rootˆcx) + b
+ *   f(0.0) = dmin
+ *   f(1.0) = dmax
+ *   b = dmin - a
+ *   a = (dmax - dmin) / (rootˆc - 1)
+ *
+ *   Inverse function:
+ *   x = log( (y-b)/a ) / log(root)
+ *
+ * 
+ * + * @author Phil Burk, (C) 2011 Mobileer Inc + */ +public class ExponentialRangeModel extends DoubleBoundedRangeModel { + + private static final Logger LOGGER = LoggerFactory.getLogger(ExponentialRangeModel.class); + private static final long serialVersionUID = -142785624892302160L; + + double a = 1.0; + double b = -1.0; + double span = 1.0; + double root = 10.0; + + /** Use default root of 10.0 and span of 1.0. */ + public ExponentialRangeModel(String name, int resolution, double dmin, double dmax, double dval) { + this(name, resolution, dmin, dmax, dval, 1.0); + } + + /** Set span before setting double value so it is translated correctly. */ + ExponentialRangeModel(String name, int resolution, double dmin, double dmax, double dval, + double span) { + super(name, resolution, dmin, dmax, dval); + setRoot(10.0); + setSpan(span); + /* Set again after coefficients setup. */ + setDoubleValue(dval); + } + + private void updateCoefficients() { + a = (getDoubleMaximum() - getDoubleMinimum()) / (Math.pow(root, span) - 1.0); + b = getDoubleMinimum() - a; + } + + private void setRoot(double w) { + root = w; + updateCoefficients(); + } + + public double getRoot() { + return root; + } + + public void setSpan(double c) { + this.span = c; + updateCoefficients(); + } + + public double getSpan() { + return span; + } + + @Override + public double sliderToDouble(int sliderValue) { + updateCoefficients(); // TODO optimize when we call this + double x = (double) sliderValue / getMaximum(); + return (a * Math.pow(root, span * x)) + b; + } + + @Override + public int doubleToSlider(double dval) { + updateCoefficients(); // TODO optimize when we call this + double z = (dval - b) / a; + double x = Math.log(z) / (span * Math.log(root)); + return (int) Math.round(x * getMaximum()); + } + + public void test(int sliderValue) { + double dval = sliderToDouble(sliderValue); + int ival = doubleToSlider(dval); + LOGGER.debug(sliderValue + " => " + dval + " => " + ival); + } + +} diff --git a/src/main/java/com/jsyn/swing/InstrumentBrowser.java b/src/main/java/com/jsyn/swing/InstrumentBrowser.java new file mode 100644 index 0000000..8e74660 --- /dev/null +++ b/src/main/java/com/jsyn/swing/InstrumentBrowser.java @@ -0,0 +1,117 @@ +/* + * Copyright 2012 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.swing; + +import java.awt.Dimension; +import java.awt.GridLayout; +import java.util.ArrayList; + +import javax.swing.JList; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.ListSelectionModel; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; + +import com.jsyn.util.InstrumentLibrary; +import com.jsyn.util.VoiceDescription; + +/** + * Display a list of VoiceDescriptions and their associated presets. Notify PresetSelectionListeners + * when a preset is selected. + * + * @author Phil Burk (C) 2012 Mobileer Inc + */ +@SuppressWarnings("serial") +public class InstrumentBrowser extends JPanel { + private InstrumentLibrary library; + private JScrollPane listScroller2; + private VoiceDescription voiceDescription; + private ArrayList listeners = new ArrayList<>(); + + public InstrumentBrowser(InstrumentLibrary library) { + this.library = library; + JPanel horizontalPanel = new JPanel(); + horizontalPanel.setLayout(new GridLayout(1, 2)); + + final JList instrumentList = new JList(library.getVoiceDescriptions()); + setupList(instrumentList); + instrumentList.addListSelectionListener(new ListSelectionListener() { + @Override + public void valueChanged(ListSelectionEvent e) { + if (!e.getValueIsAdjusting()) { + int n = instrumentList.getSelectedIndex(); + if (n >= 0) { + showPresetList(n); + } + } + } + }); + + JScrollPane listScroller1 = new JScrollPane(instrumentList); + listScroller1.setPreferredSize(new Dimension(250, 120)); + add(listScroller1); + + instrumentList.setSelectedIndex(0); + } + + public void addPresetSelectionListener(PresetSelectionListener listener) { + listeners.add(listener); + } + + public void removePresetSelectionListener(PresetSelectionListener listener) { + listeners.remove(listener); + } + + private void firePresetSelectionListeners(VoiceDescription voiceDescription, int presetIndex) { + for (PresetSelectionListener listener : listeners) { + listener.presetSelected(voiceDescription, presetIndex); + } + } + + private void showPresetList(int n) { + if (listScroller2 != null) { + remove(listScroller2); + } + voiceDescription = library.getVoiceDescriptions()[n]; + final JList presetList = new JList(voiceDescription.getPresetNames()); + setupList(presetList); + presetList.addListSelectionListener(new ListSelectionListener() { + @Override + public void valueChanged(ListSelectionEvent e) { + if (e.getValueIsAdjusting() == false) { + int n = presetList.getSelectedIndex(); + if (n >= 0) { + firePresetSelectionListeners(voiceDescription, n); + } + } + } + }); + + listScroller2 = new JScrollPane(presetList); + listScroller2.setPreferredSize(new Dimension(250, 120)); + add(listScroller2); + presetList.setSelectedIndex(0); + validate(); + } + + private void setupList(@SuppressWarnings("rawtypes") JList list) { + list.setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION); + list.setLayoutOrientation(JList.VERTICAL); + list.setVisibleRowCount(-1); + } +} diff --git a/src/main/java/com/jsyn/swing/JAppletFrame.java b/src/main/java/com/jsyn/swing/JAppletFrame.java new file mode 100644 index 0000000..53bd65b --- /dev/null +++ b/src/main/java/com/jsyn/swing/JAppletFrame.java @@ -0,0 +1,65 @@ +/* + * Copyright 1997 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.swing; + +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; + +import javax.swing.JApplet; +import javax.swing.JFrame; + +/** + * Frame that allows a program to be run as either an Application or an Applet. Used by JSyn example + * programs. + * + * @author (C) 1997 Phil Burk, SoftSynth.com + */ + +public class JAppletFrame extends JFrame { + private static final long serialVersionUID = -6047247494856379114L; + JApplet applet; + + public JAppletFrame(String frameTitle, final JApplet pApplet) { + super(frameTitle); + this.applet = pApplet; + getContentPane().add(applet); + repaint(); + + addWindowListener(new WindowAdapter() { + @Override + public void windowClosing(WindowEvent e) { + applet.stop(); + applet.destroy(); + try { + System.exit(0); + } catch (SecurityException exc) { + System.err.println("System.exit(0) not allowed by Java VM."); + } + } + + @Override + public void windowClosed(WindowEvent e) { + } + }); + } + + public void test() { + applet.init(); + applet.start(); + } + +} diff --git a/src/main/java/com/jsyn/swing/PortBoundedRangeModel.java b/src/main/java/com/jsyn/swing/PortBoundedRangeModel.java new file mode 100644 index 0000000..a5cf841 --- /dev/null +++ b/src/main/java/com/jsyn/swing/PortBoundedRangeModel.java @@ -0,0 +1,45 @@ +/* + * Copyright 2011 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.swing; + +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +import com.jsyn.ports.UnitInputPort; + +/** + * A bounded range model that drives a UnitInputPort. The range of the model is set based on the min + * and max of the port. + * + * @author Phil Burk (C) 2011 Mobileer Inc + */ +public class PortBoundedRangeModel extends DoubleBoundedRangeModel { + private static final long serialVersionUID = -8011867146560305808L; + private UnitInputPort port; + + public PortBoundedRangeModel(UnitInputPort pPort) { + super(pPort.getName(), 10000, pPort.getMinimum(), pPort.getMaximum(), pPort.getValue()); + this.port = pPort; + addChangeListener(new ChangeListener() { + @Override + public void stateChanged(ChangeEvent e) { + port.set(getDoubleValue()); + } + }); + } + +} diff --git a/src/main/java/com/jsyn/swing/PortControllerFactory.java b/src/main/java/com/jsyn/swing/PortControllerFactory.java new file mode 100644 index 0000000..a73d047 --- /dev/null +++ b/src/main/java/com/jsyn/swing/PortControllerFactory.java @@ -0,0 +1,60 @@ +/* + * Copyright 2010 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.swing; + +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +import com.jsyn.ports.UnitInputPort; + +/** + * Factory class for making various controllers for JSyn ports. + * + * @author Phil Burk (C) 2010 Mobileer Inc + */ +public class PortControllerFactory { + private static final int RESOLUTION = 100000; + + public static DoubleBoundedRangeSlider createPortSlider(final UnitInputPort port) { + DoubleBoundedRangeModel rangeModel = new DoubleBoundedRangeModel(port.getName(), + RESOLUTION, port.getMinimum(), port.getMaximum(), port.get()); + rangeModel.addChangeListener(new ChangeListener() { + @Override + public void stateChanged(ChangeEvent e) { + DoubleBoundedRangeModel model = (DoubleBoundedRangeModel) e.getSource(); + double value = model.getDoubleValue(); + port.set(value); + } + }); + return new DoubleBoundedRangeSlider(rangeModel, 4); + } + + public static DoubleBoundedRangeSlider createExponentialPortSlider(final UnitInputPort port) { + ExponentialRangeModel rangeModel = new ExponentialRangeModel(port.getName(), RESOLUTION, + port.getMinimum(), port.getMaximum(), port.get()); + rangeModel.addChangeListener(new ChangeListener() { + @Override + public void stateChanged(ChangeEvent e) { + ExponentialRangeModel model = (ExponentialRangeModel) e.getSource(); + double value = model.getDoubleValue(); + port.set(value); + } + }); + return new DoubleBoundedRangeSlider(rangeModel, 4); + } + +} diff --git a/src/main/java/com/jsyn/swing/PortModelFactory.java b/src/main/java/com/jsyn/swing/PortModelFactory.java new file mode 100644 index 0000000..8bec76a --- /dev/null +++ b/src/main/java/com/jsyn/swing/PortModelFactory.java @@ -0,0 +1,64 @@ +/* + * 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.swing; + +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +import com.jsyn.ports.UnitInputPort; + +public class PortModelFactory { + private static final int RESOLUTION = 1000000; + + public static DoubleBoundedRangeModel createLinearModel(final UnitInputPort pPort) { + final DoubleBoundedRangeModel model = new DoubleBoundedRangeModel(pPort.getName(), + RESOLUTION, pPort.getMinimum(), pPort.getMaximum(), pPort.get()); + model.addChangeListener(new ChangeListener() { + @Override + public void stateChanged(ChangeEvent e) { + pPort.set(model.getDoubleValue()); + } + }); + return model; + } + + public static ExponentialRangeModel createExponentialModel(final UnitInputPort pPort) { + final ExponentialRangeModel model = new ExponentialRangeModel(pPort.getName(), RESOLUTION, + pPort.getMinimum(), pPort.getMaximum(), pPort.get()); + model.addChangeListener(new ChangeListener() { + @Override + public void stateChanged(ChangeEvent e) { + pPort.set(model.getDoubleValue()); + } + }); + return model; + } + + public static ExponentialRangeModel createExponentialModel(final int partNum, + final UnitInputPort pPort) { + final ExponentialRangeModel model = new ExponentialRangeModel(pPort.getName(), RESOLUTION, + pPort.getMinimum(), pPort.getMaximum(), pPort.get()); + model.addChangeListener(new ChangeListener() { + @Override + public void stateChanged(ChangeEvent e) { + pPort.set(partNum, model.getDoubleValue()); + } + }); + return model; + } + +} diff --git a/src/main/java/com/jsyn/swing/PresetSelectionListener.java b/src/main/java/com/jsyn/swing/PresetSelectionListener.java new file mode 100644 index 0000000..daf0310 --- /dev/null +++ b/src/main/java/com/jsyn/swing/PresetSelectionListener.java @@ -0,0 +1,23 @@ +/* + * 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.swing; + +import com.jsyn.util.VoiceDescription; + +public interface PresetSelectionListener { + public void presetSelected(VoiceDescription voiceDescription, int presetIndex); +} diff --git a/src/main/java/com/jsyn/swing/RotaryController.java b/src/main/java/com/jsyn/swing/RotaryController.java new file mode 100644 index 0000000..c26c37f --- /dev/null +++ b/src/main/java/com/jsyn/swing/RotaryController.java @@ -0,0 +1,335 @@ +/* + * Copyright 2010 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.swing; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.RenderingHints; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.awt.event.MouseMotionAdapter; + +import javax.swing.BoundedRangeModel; +import javax.swing.JPanel; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +/** + * Rotary controller looks like a knob on a synthesizer. You control this knob by clicking on it and + * dragging up or down. If you move the mouse to the left of the knob then you + * will have coarse control. If you move the mouse to the right of the knob then you + * will have fine control. + *

+ * + * @author (C) 2010 Phil Burk, Mobileer Inc + * @version 16.1 + */ +public class RotaryController extends JPanel { + private static final long serialVersionUID = 6681532871556659546L; + private static final double SENSITIVITY = 0.01; + private final BoundedRangeModel model; + + private final double minAngle = 1.4 * Math.PI; + private final double maxAngle = -0.4 * Math.PI; + private final double unitIncrement = 0.01; + private int lastY; + private int startX; + private Color knobColor = Color.LIGHT_GRAY; + private Color lineColor = Color.RED; + private double baseValue; + + public enum Style { + LINE, LINEDOT, ARROW, ARC + }; + + private Style style = Style.ARC; + + public RotaryController(BoundedRangeModel model) { + this.model = model; + setMinimumSize(new Dimension(50, 50)); + setPreferredSize(new Dimension(50, 50)); + addMouseListener(new MouseHandler()); + addMouseMotionListener(new MouseMotionHandler()); + model.addChangeListener(new ChangeListener() { + @Override + public void stateChanged(ChangeEvent e) { + safeRepaint(); + } + + }); + } + + // This can be overridden in subclasses to workaround OpenJDK bugs. + public void safeRepaint() { + repaint(); + } + + public BoundedRangeModel getModel() { + return model; + } + + private class MouseHandler extends MouseAdapter { + + @Override + public void mousePressed(MouseEvent e) { + lastY = e.getY(); + startX = e.getX(); + } + + @Override + public void mouseReleased(MouseEvent e) { + if (isEnabled()) { + setKnobByXY(e.getX(), e.getY()); + } + } + } + + private class MouseMotionHandler extends MouseMotionAdapter { + @Override + public void mouseDragged(MouseEvent e) { + if (isEnabled()) { + setKnobByXY(e.getX(), e.getY()); + } + } + } + + private int getModelRange() { + return (((model.getMaximum() - model.getExtent()) - model.getMinimum())); + } + + /** + * A fractional value is useful for drawing. + * + * @return model value as a normalized fraction between 0.0 and 1.0 + */ + public double getFractionFromModel() { + double value = model.getValue(); + return convertValueToFraction(value); + } + + private double convertValueToFraction(double value) { + return (value - model.getMinimum()) / getModelRange(); + } + + private void setKnobByXY(int x, int y) { + // Scale increment by X position. + int xdiff = startX - x; // More to left causes bigger increments. + double power = xdiff * SENSITIVITY; + double perPixel = unitIncrement * Math.pow(2.0, power); + + int ydiff = lastY - y; + double fractionalDelta = ydiff * perPixel; + // Only update the model if we actually change values. + // This is needed in case the range is small. + int valueDelta = (int) Math.round(fractionalDelta * getModelRange()); + if (valueDelta != 0) { + model.setValue(model.getValue() + valueDelta); + lastY = y; + } + } + + private double fractionToAngle(double fraction) { + return (fraction * (maxAngle - minAngle)) + minAngle; + } + + private void drawLineIndicator(Graphics g, int x, int y, int radius, double angle, + boolean drawDot) { + double arrowSize = radius * 0.95; + int arrowX = (int) (arrowSize * Math.sin(angle)); + int arrowY = (int) (arrowSize * Math.cos(angle)); + g.setColor(lineColor); + g.drawLine(x, y, x + arrowX, y - arrowY); + if (drawDot) { + // draw little dot at end + double dotScale = 0.1; + int dotRadius = (int) (dotScale * arrowSize); + if (dotRadius > 1) { + int dotX = x + (int) ((0.99 - dotScale) * arrowX) - dotRadius; + int dotY = y - (int) ((0.99 - dotScale) * arrowY) - dotRadius; + g.fillOval(dotX, dotY, dotRadius * 2, dotRadius * 2); + } + } + } + + private void drawArrowIndicator(Graphics g, int x0, int y0, int radius, double angle) { + int arrowSize = (int) (radius * 0.95); + int arrowWidth = (int) (radius * 0.2); + int xp[] = { + 0, arrowWidth, 0, -arrowWidth + }; + int yp[] = { + arrowSize, -arrowSize / 2, 0, -arrowSize / 2 + }; + double sa = Math.sin(angle); + double ca = Math.cos(angle); + for (int i = 0; i < xp.length; i++) { + int x = xp[i]; + int y = yp[i]; + xp[i] = x0 - (int) ((x * ca) - (y * sa)); + yp[i] = y0 - (int) ((x * sa) + (y * ca)); + } + g.fillPolygon(xp, yp, xp.length); + } + + private void drawArcIndicator(Graphics g, int x, int y, int radius, double angle) { + final double DEGREES_PER_RADIAN = 180.0 / Math.PI; + final int minAngleDegrees = (int) (minAngle * DEGREES_PER_RADIAN); + final int maxAngleDegrees = (int) (maxAngle * DEGREES_PER_RADIAN); + + int zeroAngleDegrees = (int) (fractionToAngle(baseValue) * DEGREES_PER_RADIAN); + + double arrowSize = radius * 0.95; + int arcX = x - radius; + int arcY = y - radius; + int arcAngle = (int) (angle * DEGREES_PER_RADIAN); + int arrowX = (int) (arrowSize * Math.cos(angle)); + int arrowY = (int) (arrowSize * Math.sin(angle)); + + g.setColor(knobColor.darker().darker()); + g.fillArc(arcX, arcY, 2 * radius, 2 * radius, minAngleDegrees, maxAngleDegrees + - minAngleDegrees); + g.setColor(Color.ORANGE); + g.fillArc(arcX, arcY, 2 * radius, 2 * radius, zeroAngleDegrees, arcAngle - zeroAngleDegrees); + + // fill in middle + int arcWidth = radius / 4; + int diameter = ((radius - arcWidth) * 2); + g.setColor(knobColor); + g.fillOval(arcWidth + x - radius, arcWidth + y - radius, diameter, diameter); + + g.setColor(lineColor); + g.drawLine(x, y, x + arrowX, y - arrowY); + + } + + /** + * Override this method if you want to draw your own line or dot on the knob. + */ + public void drawIndicator(Graphics g, int x, int y, int radius, double angle) { + g.setColor(isEnabled() ? lineColor : lineColor.darker()); + switch (style) { + case LINE: + drawLineIndicator(g, x, y, radius, angle, false); + break; + case LINEDOT: + drawLineIndicator(g, x, y, radius, angle, true); + break; + case ARROW: + drawArrowIndicator(g, x, y, radius, angle); + break; + case ARC: + drawArcIndicator(g, x, y, radius, angle); + break; + } + } + + /** + * Override this method if you want to draw your own knob. + * + * @param g graphics context + * @param x position of center of knob + * @param y position of center of knob + * @param radius of knob in pixels + * @param angle in radians. Zero is straight up. + */ + public void drawKnob(Graphics g, int x, int y, int radius, double angle) { + Graphics2D g2 = (Graphics2D) g; + g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + + int diameter = radius * 2; + // Draw shaded side. + g.setColor(knobColor.darker()); + g.fillOval(x - radius + 2, y - radius + 2, diameter, diameter); + g.setColor(knobColor); + g.fillOval(x - radius, y - radius, diameter, diameter); + + // Draw line or other indicator of knob position. + drawIndicator(g, x, y, radius, angle); + } + + // Draw the round knob based on the current size and model value. + // This used to have a bug where the scope would draw in this components background. + // Then I changed it from overriding paint() to overriding paintComponent() and it worked. + @Override + public void paintComponent(Graphics g) { + super.paintComponent(g); + + int width = getWidth(); + int height = getHeight(); + int x = width / 2; + int y = height / 2; + + // Calculate radius from size of component. + int diameter = (width < height) ? width : height; + diameter -= 4; + int radius = diameter / 2; + + double angle = fractionToAngle(getFractionFromModel()); + drawKnob(g, x, y, radius, angle); + } + + public Color getKnobColor() { + return knobColor; + } + + /** + * @param knobColor color of body of knob + */ + public void setKnobColor(Color knobColor) { + this.knobColor = knobColor; + } + + public Color getLineColor() { + return lineColor; + } + + /** + * @param lineColor color of indicator on knob like a line or arrow + */ + public void setLineColor(Color lineColor) { + this.lineColor = lineColor; + } + + public void setStyle(Style style) { + this.style = style; + } + + public Style getStyle() { + return style; + } + + public double getBaseValue() { + return baseValue; + } + + /* + * Specify where the orange arc originates. For example a pan knob with a centered arc would + * have a baseValue of 0.5. + * @param baseValue a fraction between 0.0 and 1.0. + */ + public void setBaseValue(double baseValue) { + if (baseValue < 0.0) { + baseValue = 0.0; + } else if (baseValue > 1.0) { + baseValue = 1.0; + } + this.baseValue = baseValue; + } + +} diff --git a/src/main/java/com/jsyn/swing/RotaryTextController.java b/src/main/java/com/jsyn/swing/RotaryTextController.java new file mode 100644 index 0000000..81d6614 --- /dev/null +++ b/src/main/java/com/jsyn/swing/RotaryTextController.java @@ -0,0 +1,53 @@ +/* + * Copyright 2010 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.swing; + +import java.awt.BorderLayout; + +import javax.swing.BorderFactory; +import javax.swing.JPanel; + +/** + * Combine a RotaryController and a DoubleBoundedTextField into a convenient package. + * + * @author Phil Burk (C) 2010 Mobileer Inc + */ +public class RotaryTextController extends JPanel { + private static final long serialVersionUID = -2931828326251895375L; + private RotaryController rotary; + private DoubleBoundedTextField textField; + + public RotaryTextController(DoubleBoundedRangeModel pModel, int numDigits) { + rotary = new RotaryController(pModel); + textField = new DoubleBoundedTextField(pModel, numDigits); + setLayout(new BorderLayout()); + add(rotary, BorderLayout.CENTER); + add(textField, BorderLayout.SOUTH); + } + + /** Display the title in a border. */ + public void setTitle(String label) { + setBorder(BorderFactory.createTitledBorder(label)); + } + + @Override + public void setEnabled(boolean enabled) { + super.setEnabled(enabled); + rotary.setEnabled(enabled); + textField.setEnabled(enabled); + } +} diff --git a/src/main/java/com/jsyn/swing/SoundTweaker.java b/src/main/java/com/jsyn/swing/SoundTweaker.java new file mode 100644 index 0000000..dc48c8f --- /dev/null +++ b/src/main/java/com/jsyn/swing/SoundTweaker.java @@ -0,0 +1,120 @@ +/* + * 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.swing; + +import java.awt.Component; +import java.awt.GridLayout; +import java.util.ArrayList; +import java.util.logging.Logger; + +import javax.swing.JLabel; +import javax.swing.JPanel; + +import com.jsyn.Synthesizer; +import com.jsyn.ports.UnitInputPort; +import com.jsyn.ports.UnitPort; +import com.jsyn.unitgen.UnitGenerator; +import com.jsyn.unitgen.UnitSource; +import com.jsyn.unitgen.UnitVoice; +import com.jsyn.util.Instrument; +import com.softsynth.math.AudioMath; + +@SuppressWarnings("serial") +public class SoundTweaker extends JPanel { + private UnitSource source; + private ASCIIMusicKeyboard keyboard; + private Synthesizer synth; + + static Logger logger = Logger.getLogger(SoundTweaker.class.getName()); + + public SoundTweaker(Synthesizer synth, String title, UnitSource source) { + this.synth = synth; + this.source = source; + + setLayout(new GridLayout(0, 2)); + + UnitGenerator ugen = source.getUnitGenerator(); + ArrayList sliders = new ArrayList(); + + add(new JLabel(title)); + + if (source instanceof Instrument) { + add(keyboard = createPolyphonicKeyboard()); + } else if (source instanceof UnitVoice) { + add(keyboard = createMonophonicKeyboard()); + } + + // Arrange the faders in a stack. + // Iterate through the ports. + for (UnitPort port : ugen.getPorts()) { + if (port instanceof UnitInputPort) { + UnitInputPort inputPort = (UnitInputPort) port; + Component slider; + // Use an exponential slider if it seems appropriate. + if ((inputPort.getMinimum() > 0.0) + && ((inputPort.getMaximum() / inputPort.getMinimum()) > 4.0)) { + slider = PortControllerFactory.createExponentialPortSlider(inputPort); + } else { + slider = PortControllerFactory.createPortSlider(inputPort); + + } + add(slider); + sliders.add(slider); + } + } + + if (keyboard != null) { + for (Component slider : sliders) { + slider.addKeyListener(keyboard.getKeyListener()); + } + } + validate(); + } + + @SuppressWarnings("serial") + private ASCIIMusicKeyboard createPolyphonicKeyboard() { + return new ASCIIMusicKeyboard() { + @Override + public void keyOff(int pitch) { + ((Instrument) source).noteOff(pitch, synth.createTimeStamp()); + } + + @Override + public void keyOn(int pitch) { + double freq = AudioMath.pitchToFrequency(pitch); + ((Instrument) source).noteOn(pitch, freq, 0.5, synth.createTimeStamp()); + } + }; + } + + @SuppressWarnings("serial") + private ASCIIMusicKeyboard createMonophonicKeyboard() { + return new ASCIIMusicKeyboard() { + @Override + public void keyOff(int pitch) { + ((UnitVoice) source).noteOff(synth.createTimeStamp()); + } + + @Override + public void keyOn(int pitch) { + double freq = AudioMath.pitchToFrequency(pitch); + ((UnitVoice) source).noteOn(freq, 0.5, synth.createTimeStamp()); + } + }; + } + +} diff --git a/src/main/java/com/jsyn/swing/XYController.java b/src/main/java/com/jsyn/swing/XYController.java new file mode 100644 index 0000000..0d97c62 --- /dev/null +++ b/src/main/java/com/jsyn/swing/XYController.java @@ -0,0 +1,132 @@ +/* + * Copyright 1997 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.swing; + +import javax.swing.JPanel; + +/** + * Root class for 2 dimensional X,Y controller for wave editors, Theremins, etc. Maps pixel + * coordinates into "world" coordinates. + * + * @author (C) 1997 Phil Burk, SoftSynth.com + */ + +public class XYController extends JPanel { + double minWorldX = 0.0; + double maxWorldX = 1.0; + double minWorldY = 0.0; + double maxWorldY = 1.0; + + public XYController() { + } + + public XYController(double minWX, double minWY, double maxWX, double maxWY) { + setMinWorldX(minWX); + setMaxWorldX(maxWX); + setMinWorldY(minWY); + setMaxWorldY(maxWY); + } + + /** + * Set minimum World coordinate value for the horizontal X dimension. The minimum value + * corresponds to the left of the component. + */ + public void setMinWorldX(double minWX) { + minWorldX = minWX; + } + + public double getMinWorldX() { + return minWorldX; + } + + /** + * Set maximum World coordinate value for the horizontal X dimension. The minimum value + * corresponds to the right of the component. + */ + public void setMaxWorldX(double maxWX) { + maxWorldX = maxWX; + } + + public double getMaxWorldX() { + return maxWorldX; + } + + /** + * Set minimum World coordinate value for the vertical Y dimension. The minimum value + * corresponds to the bottom of the component. + */ + public void setMinWorldY(double minWY) { + minWorldY = minWY; + } + + public double getMinWorldY() { + return minWorldY; + } + + /** + * Set maximum World coordinate value for the vertical Y dimension. The maximum value + * corresponds to the top of the component. + */ + public void setMaxWorldY(double maxWY) { + maxWorldY = maxWY; + } + + public double getMaxWorldY() { + return maxWorldY; + } + + /** Convert from graphics coordinates (pixels) to world coordinates. */ + public double convertGXtoWX(int gx) { + int width = getWidth(); + return minWorldX + ((maxWorldX - minWorldX) * gx) / width; + } + + public double convertGYtoWY(int gy) { + int height = getHeight(); + return minWorldY + ((maxWorldY - minWorldY) * (height - gy)) / height; + } + + /** Convert from world coordinates to graphics coordinates (pixels). */ + public int convertWXtoGX(double wx) { + int width = getWidth(); + return (int) (((wx - minWorldX) * width) / (maxWorldX - minWorldX)); + } + + public int convertWYtoGY(double wy) { + int height = getHeight(); + return height - (int) (((wy - minWorldY) * height) / (maxWorldY - minWorldY)); + } + + /** Clip wx to the min and max World X values. */ + public double clipWorldX(double wx) { + if (wx < minWorldX) + wx = minWorldX; + else if (wx > maxWorldX) + wx = maxWorldX; + return wx; + } + + /** Clip wy to the min and max World Y values. */ + public double clipWorldY(double wy) { + if (wy < minWorldY) + wy = minWorldY; + else if (wy > maxWorldY) + wy = maxWorldY; + return wy; + } + +} diff --git a/src/main/java/com/jsyn/unitgen/Add.java b/src/main/java/com/jsyn/unitgen/Add.java new file mode 100644 index 0000000..c91fc85 --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/Add.java @@ -0,0 +1,50 @@ +/* + * 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.unitgen; + +/** + * This unit performs a signed addition on its two inputs.
+ * + *

+ * output = inputA + inputB
+ * 
+ * + *
+ * Note that signals connected to an InputPort are automatically added together so you may not need + * this unit. + * + * @author Phil Burk (C) 2009 Mobileer Inc + * @version 016 + * @see MultiplyAdd + * @see Subtract + */ +public class Add extends UnitBinaryOperator { + + @Override + public void generate(int start, int limit) { + double[] aValues = inputA.getValues(); + double[] bValues = inputB.getValues(); + double[] outputs = output.getValues(); + + // LOGGER.debug("adder = " + this); + // LOGGER.debug("A = " + aValues[0]); + for (int i = start; i < limit; i++) { + outputs[i] = aValues[i] + bValues[i]; + } + // LOGGER.debug("add out = " + outputs[0]); + } +} diff --git a/src/main/java/com/jsyn/unitgen/AsymptoticRamp.java b/src/main/java/com/jsyn/unitgen/AsymptoticRamp.java new file mode 100644 index 0000000..8b51294 --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/AsymptoticRamp.java @@ -0,0 +1,81 @@ +/* + * 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.unitgen; + +import com.jsyn.ports.UnitInputPort; +import com.jsyn.ports.UnitVariablePort; + +/** + * Output approaches Input exponentially. This unit provides a slowly changing value that approaches + * its Input value exponentially. The equation is: + * + *
+ * Output = Output + Rate * (Input - Output);
+ * 
+ * + * Note that the output may never reach the value of the input. It approaches the input + * asymptotically. The Rate is calculated internally based on the value on the halfLife port. Rate + * is generally just slightly less than 1.0. + * + * @author Phil Burk (C) 2009 Mobileer Inc + * @version 016 + * @see LinearRamp + * @see ExponentialRamp + * @see ContinuousRamp + */ +public class AsymptoticRamp extends UnitFilter { + public UnitVariablePort current; + public UnitInputPort halfLife; + private double previousHalfLife = -1.0; + private double decayScalar = 0.99; + + /* Define Unit Ports used by connect() and set(). */ + public AsymptoticRamp() { + addPort(halfLife = new UnitInputPort(1, "HalfLife", 0.1)); + addPort(current = new UnitVariablePort("Current")); + } + + @Override + public void generate(int start, int limit) { + double[] outputs = output.getValues(); + double[] inputs = input.getValues(); + double currentHalfLife = halfLife.getValues()[0]; + double currentValue = current.getValue(); + double inputValue = currentValue; + + if (currentHalfLife != previousHalfLife) { + decayScalar = this.convertHalfLifeToMultiplier(currentHalfLife); + previousHalfLife = currentHalfLife; + } + + for (int i = start; i < limit; i++) { + inputValue = inputs[i]; + currentValue = currentValue + decayScalar * (inputValue - currentValue); + outputs[i] = currentValue; + } + + /* + * When current gets close to input, set current to input to prevent FP underflow, which can + * cause a severe performance degradation in 'C'. + */ + if (Math.abs(inputValue - currentValue) < VERY_SMALL_FLOAT) { + currentValue = inputValue; + } + + current.setValue(currentValue); + } +} diff --git a/src/main/java/com/jsyn/unitgen/BrownNoise.java b/src/main/java/com/jsyn/unitgen/BrownNoise.java new file mode 100644 index 0000000..e70b7f4 --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/BrownNoise.java @@ -0,0 +1,75 @@ +/* + * Copyright 1997 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.unitgen; + +import com.jsyn.ports.UnitInputPort; +import com.jsyn.ports.UnitOutputPort; +import com.jsyn.util.PseudoRandom; + +/** + * BrownNoise unit. This unit uses a pseudo-random number generator to produce a noise related to + * Brownian Motion. A DC blocker is used to prevent runaway drift. + * + *
+ * 
+ * output = (previous * (1.0 - damping)) + (random * amplitude) 
+ * 
+ * 
+ * + * The output drifts quite a bit and will generally exceed the range of +/1 amplitude. + * + * @author (C) 1997-2011 Phil Burk, Mobileer Inc + * @see WhiteNoise + * @see RedNoise + * @see PinkNoise + */ +public class BrownNoise extends UnitGenerator implements UnitSource { + private PseudoRandom randomNum; + /** Increasing the damping will effectively increase the cutoff + * frequency of a high pass filter that is used to block DC bias. + * Warning: setting this too close to zero can result in very large output values. + */ + public UnitInputPort damping; + public UnitInputPort amplitude; + public UnitOutputPort output; + private double previous; + + public BrownNoise() { + randomNum = new PseudoRandom(); + addPort(damping = new UnitInputPort("Damping")); + damping.setup(0.0001, 0.01, 0.1); + addPort(amplitude = new UnitInputPort("Amplitude", UnitOscillator.DEFAULT_AMPLITUDE)); + addPort(output = new UnitOutputPort("Output")); + } + + @Override + public void generate(int start, int limit) { + double[] amplitudes = amplitude.getValues(); + double[] outputs = output.getValues(); + double damper = 1.0 - damping.getValues()[0]; + + for (int i = start; i < limit; i++) { + double r = randomNum.nextRandomDouble() * amplitudes[i]; + outputs[i] = previous = (damper * previous) + r; + } + } + + @Override + public UnitOutputPort getOutput() { + return output; + } +} diff --git a/src/main/java/com/jsyn/unitgen/ChannelIn.java b/src/main/java/com/jsyn/unitgen/ChannelIn.java new file mode 100644 index 0000000..c440b4f --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/ChannelIn.java @@ -0,0 +1,59 @@ +/* + * 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.unitgen; + +import com.jsyn.ports.UnitOutputPort; + +/** + * Provides access to one specific channel of the audio input. For ChannelIn to work you must call + * the {@link com.jsyn.Synthesizer} start() method with numInputChannels > 0. + * + * @author Phil Burk (C) 2009 Mobileer Inc + * @see ChannelOut + * @see LineIn + */ +public class ChannelIn extends UnitGenerator { + public UnitOutputPort output; + private int channelIndex; + + public ChannelIn() { + this(0); + } + + public ChannelIn(int channelIndex) { + addPort(output = new UnitOutputPort()); + setChannelIndex(channelIndex); + } + + public int getChannelIndex() { + return channelIndex; + } + + public void setChannelIndex(int channelIndex) { + this.channelIndex = channelIndex; + } + + @Override + public void generate(int start, int limit) { + double[] outputs = output.getValues(0); + double[] buffer = synthesisEngine.getInputBuffer(channelIndex); + for (int i = start; i < limit; i++) { + outputs[i] = buffer[i]; + } + } + +} diff --git a/src/main/java/com/jsyn/unitgen/ChannelOut.java b/src/main/java/com/jsyn/unitgen/ChannelOut.java new file mode 100644 index 0000000..8ef0677 --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/ChannelOut.java @@ -0,0 +1,62 @@ +/* + * 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.unitgen; + +import com.jsyn.ports.UnitInputPort; + +/** + * Provides access to one channel of the audio output. + * For more than two channels you must call + * the {@link com.jsyn.Synthesizer} start() method with numOutputChannels > 2. + * + * @author Phil Burk (C) 2009 Mobileer Inc + * @see ChannelIn + */ +public class ChannelOut extends UnitGenerator { + public UnitInputPort input; + private int channelIndex; + + public ChannelOut() { + addPort(input = new UnitInputPort("Input")); + } + + public int getChannelIndex() { + return channelIndex; + } + + public void setChannelIndex(int channelIndex) { + this.channelIndex = channelIndex; + } + + /** + * This unit won't do anything unless you start() it. + */ + @Override + public boolean isStartRequired() { + return true; + } + + @Override + public void generate(int start, int limit) { + double[] inputs = input.getValues(0); + double[] buffer = synthesisEngine.getOutputBuffer(channelIndex); + for (int i = start; i < limit; i++) { + buffer[i] += inputs[i]; + } + } + +} diff --git a/src/main/java/com/jsyn/unitgen/Circuit.java b/src/main/java/com/jsyn/unitgen/Circuit.java new file mode 100644 index 0000000..01cb860 --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/Circuit.java @@ -0,0 +1,122 @@ +/* + * 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.unitgen; + +import java.util.ArrayList; +import java.util.LinkedHashMap; + +import com.jsyn.engine.SynthesisEngine; +import com.jsyn.ports.UnitPort; + +/** + * Contains a list of units that are executed together. + * + * @author Phil Burk (C) 2009 Mobileer Inc + */ +public class Circuit extends UnitGenerator { + private ArrayList units = new ArrayList(); + + private final LinkedHashMap portAliases = new LinkedHashMap(); + + @Override + public void generate(int start, int limit) { + for (UnitGenerator unit : units) { + unit.generate(start, limit); + } + } + + /** + * Call flattenOutputs on subunits. Flatten output ports so we don't output a changing signal + * when stopped. + */ + @Override + public void flattenOutputs() { + for (UnitGenerator unit : units) { + unit.flattenOutputs(); + } + } + + /** + * Call setEnabled on subunits. + */ + @Override + public void setEnabled(boolean enabled) { + super.setEnabled(enabled); + for (UnitGenerator unit : units) { + unit.setEnabled(enabled); + } + } + + /** + * @deprecated ignored, frameRate comes from the SynthesisEngine + * @param frameRate + */ + @Deprecated + @Override + public void setFrameRate(int frameRate) { + super.setFrameRate(frameRate); + for (UnitGenerator unit : units) { + unit.setFrameRate(frameRate); + } + } + + @Override + public void setSynthesisEngine(SynthesisEngine engine) { + super.setSynthesisEngine(engine); + for (UnitGenerator unit : units) { + unit.setSynthesisEngine(engine); + } + } + + /** Add a unit to the circuit. */ + public void add(UnitGenerator unit) { + units.add(unit); + unit.setCircuit(this); + // Propagate circuit properties down into subunits. + unit.setEnabled(isEnabled()); + } + + public void usePreset(int presetIndex) { + } + + + /** + * Add an alternate name for looking up a port. + * @param port + * @param alias + */ + public void addPortAlias(UnitPort port, String alias) { + // Store in a hash table by an alternate name. + portAliases.put(alias.toLowerCase(), port); + } + + + /** + * Case-insensitive search for a port by its name or alias. + * @param portName + * @return matching port or null + */ + @Override + public UnitPort getPortByName(String portName) { + UnitPort port = super.getPortByName(portName); + if (port == null) { + port = portAliases.get(portName.toLowerCase()); + } + return port; + } + +} diff --git a/src/main/java/com/jsyn/unitgen/Compare.java b/src/main/java/com/jsyn/unitgen/Compare.java new file mode 100644 index 0000000..7de2e53 --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/Compare.java @@ -0,0 +1,38 @@ +/* + * 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.unitgen; + +/** + * + Output 1.0 if inputA > inputB. Otherwise output 0.0. + * + * @author Phil Burk (C) 2009 Mobileer Inc + * @version 016 + * @see Maximum + */ +public class Compare extends UnitBinaryOperator { + @Override + public void generate(int start, int limit) { + double[] aValues = inputA.getValues(); + double[] bValues = inputB.getValues(); + double[] outputs = output.getValues(); + + for (int i = start; i < limit; i++) { + outputs[i] = (aValues[i] > bValues[i]) ? 1.0 : 0.0; + } + } +} diff --git a/src/main/java/com/jsyn/unitgen/ContinuousRamp.java b/src/main/java/com/jsyn/unitgen/ContinuousRamp.java new file mode 100644 index 0000000..dd90445 --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/ContinuousRamp.java @@ -0,0 +1,91 @@ +/* + * 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.unitgen; + +import com.jsyn.ports.UnitInputPort; +import com.jsyn.ports.UnitVariablePort; + +/** + * A ramp whose function over time is continuous in value and in slope. Also called an "S curve". + * + * @author Phil Burk (C) 2009 Mobileer Inc + * @version 016 + * @see LinearRamp + * @see ExponentialRamp + * @see AsymptoticRamp + */ +public class ContinuousRamp extends UnitFilter { + public UnitVariablePort current; + /** + * Time it takes to get from current value to input value when input is changed. Default value + * is 1.0 seconds. + */ + public UnitInputPort time; + private double previousInput = Double.MIN_VALUE; + // Coefficients for cubic polynomial. + private double a; + private double b; + private double d; + private int framesLeft; + + /* Define Unit Ports used by connect() and set(). */ + public ContinuousRamp() { + addPort(time = new UnitInputPort(1, "Time", 1.0)); + addPort(current = new UnitVariablePort("Current")); + } + + @Override + public void generate(int start, int limit) { + double[] outputs = output.getValues(); + double[] inputs = input.getValues(); + double currentTime = time.getValues()[0]; + double currentValue = current.getValue(); + double inputValue = currentValue; + + for (int i = start; i < limit; i++) { + inputValue = inputs[i]; + double x; + if (inputValue != previousInput) { + x = framesLeft; + // Calculate coefficients. + double currentSlope = x * ((3 * a * x) + (2 * b)); + + framesLeft = (int) (getSynthesisEngine().getFrameRate() * currentTime); + if (framesLeft < 1) { + framesLeft = 1; + } + x = framesLeft; + // Calculate coefficients. + d = inputValue; + double xsq = x * x; + b = ((3 * currentValue) - (currentSlope * x) - (3 * d)) / xsq; + a = (currentSlope - (2 * b * x)) / (3 * xsq); + previousInput = inputValue; + } + + if (framesLeft > 0) { + x = --framesLeft; + // Cubic polynomial. c==0 + currentValue = (x * (x * ((x * a) + b))) + d; + } + + outputs[i] = currentValue; + } + + current.setValue(currentValue); + } +} diff --git a/src/main/java/com/jsyn/unitgen/CrossFade.java b/src/main/java/com/jsyn/unitgen/CrossFade.java new file mode 100644 index 0000000..4375fa6 --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/CrossFade.java @@ -0,0 +1,60 @@ +/* + * 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.unitgen; + +import com.jsyn.ports.UnitInputPort; +import com.jsyn.ports.UnitOutputPort; + +/** + * Linear CrossFade between parts of the input. + *

+ * Mix input[0] and input[1] based on the value of "fade". When fade is -1, output is all input[0]. + * When fade is 0, output is half input[0] and half input[1]. When fade is +1, output is all + * input[1]. + *

+ * + * @author Phil Burk (C) 2009 Mobileer Inc + * @version 016 + * @see Pan + */ +public class CrossFade extends UnitGenerator { + public UnitInputPort input; + public UnitInputPort fade; + public UnitOutputPort output; + + /* Define Unit Ports used by connect() and set(). */ + public CrossFade() { + addPort(input = new UnitInputPort(2, "Input")); + addPort(fade = new UnitInputPort("Fade")); + fade.setup(-1.0, 0.0, 1.0); + addPort(output = new UnitOutputPort()); + } + + @Override + public void generate(int start, int limit) { + double[] input0s = input.getValues(0); + double[] input1s = input.getValues(1); + double[] fades = fade.getValues(); + double[] outputs = output.getValues(); + for (int i = start; i < limit; i++) { + // Scale and offset to 0.0 to 1.0 range. + double gain = (fades[i] * 0.5) + 0.5; + outputs[i] = (input0s[i] * (1.0 - gain)) + (input1s[i] * gain); + } + } + +} diff --git a/src/main/java/com/jsyn/unitgen/Delay.java b/src/main/java/com/jsyn/unitgen/Delay.java new file mode 100644 index 0000000..aa450a9 --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/Delay.java @@ -0,0 +1,57 @@ +/* + * 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.unitgen; + +/** + * Simple non-interpolating delay. The delay line must be allocated by calling allocate(n). + * + * @author Phil Burk (C) 2009 Mobileer Inc + * @version 016 + * @see InterpolatingDelay + */ + +public class Delay extends UnitFilter { + private float[] buffer; + private int cursor; + private int numSamples; + + /** + * Allocate an internal array for the delay line. + * + * @param numSamples + */ + public void allocate(int numSamples) { + this.numSamples = numSamples; + buffer = new float[numSamples]; + } + + @Override + public void generate(int start, int limit) { + double[] inputs = input.getValues(); + double[] outputs = output.getValues(); + + for (int i = start; i < limit; i++) { + outputs[i] = buffer[cursor]; + buffer[cursor] = (float) inputs[i]; + cursor += 1; + if (cursor >= numSamples) { + cursor = 0; + } + } + } + +} diff --git a/src/main/java/com/jsyn/unitgen/Divide.java b/src/main/java/com/jsyn/unitgen/Divide.java new file mode 100644 index 0000000..cddcd7c --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/Divide.java @@ -0,0 +1,53 @@ +/* + * 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.unitgen; + +/** + * This unit divides its two inputs.
+ * + *

+ * output = inputA / inputB
+ * 
+ * + *
+ * Note that this unit is protected from dividing by zero. But you can still get some very big + * outputs. + * + * @author Phil Burk (C) 2009 Mobileer Inc + * @version 016 + * @see Multiply + * @see Subtract + */ +public class Divide extends UnitBinaryOperator { + + @Override + public void generate(int start, int limit) { + double[] aValues = inputA.getValues(); + double[] bValues = inputB.getValues(); + double[] outputs = output.getValues(); + for (int i = start; i < limit; i++) { + /* Prevent divide by zero crash. */ + double b = bValues[i]; + if (b == 0.0) { + b = 0.0000001; + } + + outputs[i] = aValues[i] / b; + } + } + +} diff --git a/src/main/java/com/jsyn/unitgen/DualInTwoOut.java b/src/main/java/com/jsyn/unitgen/DualInTwoOut.java new file mode 100644 index 0000000..ec7dff5 --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/DualInTwoOut.java @@ -0,0 +1,59 @@ +/* + * 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.unitgen; + +import com.jsyn.ports.UnitInputPort; +import com.jsyn.ports.UnitOutputPort; + +/** + * This unit splits a dual (stereo) input to two discrete outputs.
+ * + *
+ * outputA = input[0];
+ * outputB = input[1];
+ * 
+ * + *
+ * + * @author Phil Burk (C) 2009 Mobileer Inc + * @version 016 + */ + +public class DualInTwoOut extends UnitGenerator { + public UnitInputPort input; + public UnitOutputPort outputA; + public UnitOutputPort outputB; + + public DualInTwoOut() { + addPort(input = new UnitInputPort(2, "Input")); + addPort(outputA = new UnitOutputPort("OutputA")); + addPort(outputB = new UnitOutputPort("OutputB")); + } + + @Override + public void generate(int start, int limit) { + double[] input0s = input.getValues(0); + double[] input1s = input.getValues(1); + double[] outputAs = outputA.getValues(); + double[] outputBs = outputB.getValues(); + + for (int i = start; i < limit; i++) { + outputAs[i] = input0s[i]; + outputBs[i] = input1s[i]; + } + } +} diff --git a/src/main/java/com/jsyn/unitgen/EdgeDetector.java b/src/main/java/com/jsyn/unitgen/EdgeDetector.java new file mode 100644 index 0000000..e314f7d --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/EdgeDetector.java @@ -0,0 +1,44 @@ +/* + * Copyright 1997 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.unitgen; + +/** + * Output 1.0 if the input crosses from zero while rising. Otherwise output zero. The output is a + * single sample wide impulse. This can be used with a Latch to implement a "sample and hold" + * circuit. + * + * @author (C) 1997-2010 Phil Burk, Mobileer Inc + * @see Latch + */ +public class EdgeDetector extends UnitFilter { + private double previous = 0.0; + + public EdgeDetector() { + } + + @Override + public void generate(int start, int limit) { + double[] inputs = input.getValues(); + double[] outputs = output.getValues(); + + for (int i = start; i < limit; i++) { + double in = inputs[i]; + outputs[i] = ((previous <= 0.0) && (in > 0.0)) ? 1.0 : 0.0; + previous = in; + } + } +} diff --git a/src/main/java/com/jsyn/unitgen/EnvelopeAttackDecay.java b/src/main/java/com/jsyn/unitgen/EnvelopeAttackDecay.java new file mode 100644 index 0000000..db3ecaa --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/EnvelopeAttackDecay.java @@ -0,0 +1,145 @@ +/* + * Copyright 2011 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.unitgen; + +import com.jsyn.engine.SynthesisEngine; +import com.jsyn.ports.UnitInputPort; + +/** + * Two stage Attack/Decay envelope that is triggered by an input level greater than THRESHOLD. This + * does not sustain. + * + * @author Phil Burk (C) 2011 Mobileer Inc + */ +public class EnvelopeAttackDecay extends UnitGate { + public static final double THRESHOLD = 0.01; + private static final double MIN_DURATION = (1.0 / 100000.0); + + /** + * Time in seconds for the rising stage of the envelope to go from 0.0 to 1.0. The attack is a + * linear ramp. + */ + public UnitInputPort attack; + /** + * Time in seconds for the falling stage to go from 0 dB to -90 dB. + */ + public UnitInputPort decay; + + public UnitInputPort amplitude; + + private enum State { + IDLE, ATTACKING, DECAYING + } + + private State state = State.IDLE; + private double scaler = 1.0; + private double level; + private double increment; + + public EnvelopeAttackDecay() { + super(); + addPort(attack = new UnitInputPort("Attack")); + attack.setup(0.001, 0.05, 8.0); + addPort(decay = new UnitInputPort("Decay")); + decay.setup(0.001, 0.2, 8.0); + addPort(amplitude = new UnitInputPort("Amplitude", 1.0)); + startIdle(); + } + + public void export(Circuit circuit, String prefix) { + circuit.addPort(attack, prefix + attack.getName()); + circuit.addPort(decay, prefix + decay.getName()); + } + + @Override + public void generate(int start, int limit) { + double[] amplitudes = amplitude.getValues(); + double[] outputs = output.getValues(); + + for (int i = start; i < limit;) { + boolean triggered = input.checkGate(i); + switch (state) { + case IDLE: + for (; i < limit; i++) { + outputs[i] = level; + if (triggered) { + startAttack(i); + break; + } + } + break; + case ATTACKING: + for (; i < limit; i++) { + // Increment first so we can render fast attacks. + level += increment; + if (level >= 1.0) { + level = 1.0; + outputs[i] = level * amplitudes[i]; + startDecay(i); + break; + } + outputs[i] = level * amplitudes[i]; + } + break; + case DECAYING: + for (; i < limit; i++) { + outputs[i] = level * amplitudes[i]; + level *= scaler; + if (triggered) { + startAttack(i); + break; + } else if (level < SynthesisEngine.DB90) { + input.checkAutoDisable(); + startIdle(); + break; + } + } + break; + } + } + } + + private void startIdle() { + state = State.IDLE; + level = 0.0; + } + + private void startAttack(int i) { + double[] attacks = attack.getValues(); + double duration = attacks[i]; + if (duration < MIN_DURATION) { + level = 1.0; + startDecay(i); + } else { + // assume going from 0.0 to 1.0 even if retriggering + increment = getFramePeriod() / duration; + state = State.ATTACKING; + } + } + + private void startDecay(int i) { + double[] decays = decay.getValues(); + double duration = decays[i]; + if (duration < MIN_DURATION) { + startIdle(); + } else { + scaler = getSynthesisEngine().convertTimeToExponentialScaler(duration); + state = State.DECAYING; + } + } + +} diff --git a/src/main/java/com/jsyn/unitgen/EnvelopeDAHDSR.java b/src/main/java/com/jsyn/unitgen/EnvelopeDAHDSR.java new file mode 100644 index 0000000..c5ebe83 --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/EnvelopeDAHDSR.java @@ -0,0 +1,294 @@ +/* + * Copyright 2010 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.unitgen; + +import com.jsyn.data.SegmentedEnvelope; +import com.jsyn.engine.SynthesisEngine; +import com.jsyn.ports.UnitInputPort; +import com.jsyn.ports.UnitOutputPort; + +/** + * Six stage envelope similar to an ADSR. DAHDSR is like an ADSR but with an additional Delay stage + * before the attack, and a Hold stage after the Attack. If Delay and Hold are both set to zero then + * it will act like an ADSR. The envelope is triggered when the input goes above THRESHOLD. The + * envelope is released when the input goes below THRESHOLD. The THRESHOLD is currently 0.01 but may + * change so it would be best to use an input signal that went from 0 to 1. Mathematically an + * exponential Release will never reach 0.0. But when it reaches -96 dB the DAHDSR just sets its + * output to 0.0 and stops. There is an example program in the ZIP archive called HearDAHDSR. It + * drives a DAHDSR with a square wave. + * + * @author Phil Burk (C) 2010 Mobileer Inc + * @see SegmentedEnvelope + */ +public class EnvelopeDAHDSR extends UnitGate implements UnitSource { + private static final double MIN_DURATION = (1.0 / 100000.0); + + /** + * Time in seconds for first stage of the envelope, before the attack. Typically zero. + */ + public UnitInputPort delay; + /** + * Time in seconds for the rising stage of the envelope to go from 0.0 to 1.0. The attack is a + * linear ramp. + */ + public UnitInputPort attack; + /** Time in seconds for the plateau between the attack and decay stages. */ + public UnitInputPort hold; + /** + * Time in seconds for the falling stage to go from 0 dB to -90 dB. The decay stage will stop at + * the sustain level. But we calculate the time to fall to -90 dB so that the decay + * rate will be unaffected by the sustain level. + */ + public UnitInputPort decay; + /** + * Level for the sustain stage. The envelope will hold here until the input goes to zero or + * less. This should be set between 0.0 and 1.0. + */ + public UnitInputPort sustain; + /** + * Time in seconds to go from 0 dB to -90 dB. This stage is triggered when the input goes to + * zero or less. The release stage will start from the sustain level. But we calculate the time + * to fall from full amplitude so that the release rate will be unaffected by the + * sustain level. + */ + public UnitInputPort release; + public UnitInputPort amplitude; + + enum State { + IDLE, DELAYING, ATTACKING, HOLDING, DECAYING, SUSTAINING, RELEASING + } + + private State state = State.IDLE; + private double countdown; + private double scaler = 1.0; + private double level; + private double increment; + + public EnvelopeDAHDSR() { + super(); + addPort(delay = new UnitInputPort("Delay", 0.0)); + delay.setup(0.0, 0.0, 2.0); + addPort(attack = new UnitInputPort("Attack", 0.1)); + attack.setup(0.01, 0.1, 8.0); + addPort(hold = new UnitInputPort("Hold", 0.0)); + hold.setup(0.0, 0.0, 2.0); + addPort(decay = new UnitInputPort("Decay", 0.2)); + decay.setup(0.01, 0.2, 8.0); + addPort(sustain = new UnitInputPort("Sustain", 0.5)); + sustain.setup(0.0, 0.5, 1.0); + addPort(release = new UnitInputPort("Release", 0.3)); + release.setup(0.01, 0.3, 8.0); + addPort(amplitude = new UnitInputPort("Amplitude", 1.0)); + } + + @Override + public void generate(int start, int limit) { + double[] sustains = sustain.getValues(); + double[] amplitudes = amplitude.getValues(); + double[] outputs = output.getValues(); + + for (int i = start; i < limit;) { + boolean triggered = input.checkGate(i); + switch (state) { + case IDLE: + for (; i < limit; i++) { + outputs[i] = level * amplitudes[i]; + if (triggered) { + startDelay(i); + break; + } + } + break; + + case DELAYING: + for (; i < limit; i++) { + outputs[i] = level * amplitudes[i]; + if (input.isOff()) { + startRelease(i); + break; + } else { + countdown -= 1; + if (countdown <= 0) { + startAttack(i); + break; + } + } + } + break; + + case ATTACKING: + for (; i < limit; i++) { + // Increment first so we can render fast attacks. + level += increment; + if (level >= 1.0) { + level = 1.0; + outputs[i] = level * amplitudes[i]; + startHold(i); + break; + } else { + outputs[i] = level * amplitudes[i]; + if (input.isOff()) { + startRelease(i); + break; + } + } + } + break; + + case HOLDING: + for (; i < limit; i++) { + outputs[i] = amplitudes[i]; // level is 1.0 + countdown -= 1; + if (countdown <= 0) { + startDecay(i); + break; + } else if (input.isOff()) { + startRelease(i); + break; + } + } + break; + + case DECAYING: + for (; i < limit; i++) { + outputs[i] = level * amplitudes[i]; + level *= scaler; // exponential decay + if (triggered) { + startDelay(i); + break; + } else if (level < sustains[i]) { + level = sustains[i]; + startSustain(i); + break; + } else if (level < SynthesisEngine.DB96) { + input.checkAutoDisable(); + startIdle(); + break; + } else if (input.isOff()) { + startRelease(i); + break; + } + } + break; + + case SUSTAINING: + for (; i < limit; i++) { + level = sustains[i]; + outputs[i] = level * amplitudes[i]; + if (triggered) { + startDelay(i); + break; + } else if (input.isOff()) { + startRelease(i); + break; + } + } + break; + + case RELEASING: + for (; i < limit; i++) { + outputs[i] = level * amplitudes[i]; + level *= scaler; // exponential decay + if (triggered) { + startDelay(i); + break; + } else if (level < SynthesisEngine.DB96) { + input.checkAutoDisable(); + startIdle(); + break; + } + } + break; + } + } + } + + private void startIdle() { + state = State.IDLE; + level = 0.0; + } + + private void startDelay(int i) { + double[] delays = delay.getValues(); + if (delays[i] <= 0.0) { + startAttack(i); + } else { + countdown = (int) (delays[i] * getFrameRate()); + state = State.DELAYING; + } + } + + private void startAttack(int i) { + double[] attacks = attack.getValues(); + double duration = attacks[i]; + if (duration < MIN_DURATION) { + level = 1.0; + startHold(i); + } else { + increment = getFramePeriod() / duration; + state = State.ATTACKING; + } + } + + private void startHold(int i) { + double[] holds = hold.getValues(); + if (holds[i] <= 0.0) { + startDecay(i); + } else { + countdown = (int) (holds[i] * getFrameRate()); + state = State.HOLDING; + } + } + + private void startDecay(int i) { + double[] decays = decay.getValues(); + double duration = decays[i]; + if (duration < MIN_DURATION) { + startSustain(i); + } else { + scaler = getSynthesisEngine().convertTimeToExponentialScaler(duration); + state = State.DECAYING; + } + } + + private void startSustain(int i) { + state = State.SUSTAINING; + } + + private void startRelease(int i) { + double[] releases = release.getValues(); + double duration = releases[i]; + if (duration < MIN_DURATION) { + duration = MIN_DURATION; + } + scaler = getSynthesisEngine().convertTimeToExponentialScaler(duration); + state = State.RELEASING; + } + + public void export(Circuit circuit, String prefix) { + circuit.addPort(attack, prefix + attack.getName()); + circuit.addPort(decay, prefix + decay.getName()); + circuit.addPort(sustain, prefix + sustain.getName()); + circuit.addPort(release, prefix + release.getName()); + } + + @Override + public UnitOutputPort getOutput() { + return output; + } + +} diff --git a/src/main/java/com/jsyn/unitgen/ExponentialRamp.java b/src/main/java/com/jsyn/unitgen/ExponentialRamp.java new file mode 100644 index 0000000..36159b4 --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/ExponentialRamp.java @@ -0,0 +1,104 @@ +/* + * Copyright 2010 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.unitgen; + +import com.jsyn.ports.UnitInputPort; +import com.jsyn.ports.UnitVariablePort; + +/** + * Output approaches Input exponentially and will reach it in the specified time. + * + * @author Phil Burk (C) 2010 Mobileer Inc + * @version 016 + * @see LinearRamp + * @see AsymptoticRamp + * @see ContinuousRamp + */ +public class ExponentialRamp extends UnitFilter { + public UnitInputPort time; + public UnitVariablePort current; + + private double target; + private double timeHeld = 0.0; + private double scaler = 1.0; + + public ExponentialRamp() { + addPort(time = new UnitInputPort("Time")); + input.setup(0.0001, 1.0, 1.0); + addPort(current = new UnitVariablePort("Current", 1.0)); + } + + @Override + public void generate(int start, int limit) { + double[] outputs = output.getValues(); + double currentInput = input.getValues()[0]; + double currentTime = time.getValues()[0]; + double currentValue = current.getValue(); + + if (currentTime != timeHeld) { + scaler = convertTimeToExponentialScaler(currentTime, currentValue, currentInput); + timeHeld = currentTime; + } + + // If input has changed, start new segment. + // Equality check is OK because we set them exactly equal below. + if (currentInput != target) { + scaler = convertTimeToExponentialScaler(currentTime, currentValue, currentInput); + target = currentInput; + } + + if (currentValue < target) { + // Going up. + for (int i = start; i < limit; i++) { + currentValue = currentValue * scaler; + if (currentValue > target) { + currentValue = target; + scaler = 1.0; + } + outputs[i] = currentValue; + } + } else if (currentValue > target) { + // Going down. + for (int i = start; i < limit; i++) { + currentValue = currentValue * scaler; + if (currentValue < target) { + currentValue = target; + scaler = 1.0; + } + outputs[i] = currentValue; + } + + } else if (currentValue == target) { + for (int i = start; i < limit; i++) { + outputs[i] = target; + } + } + + current.setValue(currentValue); + } + + private double convertTimeToExponentialScaler(double duration, double source, double target) { + double product = source * target; + if (product <= 0.0000001) { + throw new IllegalArgumentException( + "Exponential ramp crosses zero or gets too close to zero."); + } + // Calculate scaler so that scaler^frames = target/source + double numFrames = duration * getFrameRate(); + return Math.pow((target / source), (1.0 / numFrames)); + } +} diff --git a/src/main/java/com/jsyn/unitgen/FFT.java b/src/main/java/com/jsyn/unitgen/FFT.java new file mode 100644 index 0000000..63fce50 --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/FFT.java @@ -0,0 +1,36 @@ +/* + * Copyright 2013 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.unitgen; + +/** + * Periodically transform the complex input signal using an FFT to a complex spectral stream. This + * is probably not as useful as the SpectralFFT, which outputs complete spectra. + * + * @author Phil Burk (C) 2013 Mobileer Inc + * @see IFFT + * @see SpectralFFT + */ +public class FFT extends FFTBase { + public FFT() { + super(); + } + + @Override + protected int getSign() { + return 1; // 1 for FFT + } +} diff --git a/src/main/java/com/jsyn/unitgen/FFTBase.java b/src/main/java/com/jsyn/unitgen/FFTBase.java new file mode 100644 index 0000000..055c04b --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/FFTBase.java @@ -0,0 +1,86 @@ +/* + * Copyright 2013 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.unitgen; + +import com.jsyn.data.Spectrum; +import com.jsyn.ports.UnitInputPort; +import com.jsyn.ports.UnitOutputPort; +import com.softsynth.math.FourierMath; + +/** + * Periodically transform the complex input signal using an FFT to a complex spectral stream. + * + * @author Phil Burk (C) 2013 Mobileer Inc + * @version 016 + * @see IFFT + */ +public abstract class FFTBase extends UnitGenerator { + public UnitInputPort inputReal; + public UnitInputPort inputImaginary; + public UnitOutputPort outputReal; + public UnitOutputPort outputImaginary; + protected double[] realInput; + protected double[] realOutput; + protected double[] imaginaryInput; + protected double[] imaginaryOutput; + protected int cursor; + + protected FFTBase() { + addPort(inputReal = new UnitInputPort("InputReal")); + addPort(inputImaginary = new UnitInputPort("InputImaginary")); + addPort(outputReal = new UnitOutputPort("OutputReal")); + addPort(outputImaginary = new UnitOutputPort("OutputImaginary")); + setSize(Spectrum.DEFAULT_SIZE); + } + + public void setSize(int size) { + realInput = new double[size]; + realOutput = new double[size]; + imaginaryInput = new double[size]; + imaginaryOutput = new double[size]; + cursor = 0; + } + + public int getSize() { + return realInput.length; + } + + @Override + public void generate(int start, int limit) { + double[] inputRs = inputReal.getValues(); + double[] inputIs = inputImaginary.getValues(); + double[] outputRs = outputReal.getValues(); + double[] outputIs = outputImaginary.getValues(); + for (int i = start; i < limit; i++) { + realInput[cursor] = inputRs[i]; + imaginaryInput[cursor] = inputIs[i]; + outputRs[i] = realOutput[cursor]; + outputIs[i] = imaginaryOutput[cursor]; + cursor += 1; + // When it is full, do the FFT. + if (cursor == realInput.length) { + // Copy to output buffer so we can do the FFT in place. + System.arraycopy(realInput, 0, realOutput, 0, realInput.length); + System.arraycopy(imaginaryInput, 0, imaginaryOutput, 0, imaginaryInput.length); + FourierMath.transform(getSign(), realOutput.length, realOutput, imaginaryOutput); + cursor = 0; + } + } + } + + protected abstract int getSign(); +} diff --git a/src/main/java/com/jsyn/unitgen/FilterAllPass.java b/src/main/java/com/jsyn/unitgen/FilterAllPass.java new file mode 100644 index 0000000..749b2d6 --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/FilterAllPass.java @@ -0,0 +1,62 @@ +/* + * Copyright 2014 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.unitgen; + +import com.jsyn.ports.UnitInputPort; + +/** + * AllPass filter using the following formula: + * + *
+ * y(n) = -gain * x(n) + x(n - 1) + gain * y(n - 1)
+ * 
+ * + * where y(n) is Output, x(n) is Input, x(n-1) is a delayed copy of the input, and y(n-1) is a + * delayed copy of the output. An all-pass filter will pass all frequencies with equal amplitude. + * But it changes the phase relationships of the partials by delaying them by an amount proportional + * to their wavelength,. + * + * @author (C) 2014 Phil Burk, SoftSynth.com + * @see FilterLowPass + */ + +public class FilterAllPass extends UnitFilter { + /** Feedback gain. Should be less than 1.0. Default is 0.8. */ + public UnitInputPort gain; + + private double x1; + private double y1; + + public FilterAllPass() { + addPort(gain = new UnitInputPort("Gain", 0.8)); + } + + @Override + public void generate(int start, int limit) { + double[] inputs = input.getValues(); + double[] outputs = output.getValues(); + double g = gain.getValue(); + + for (int i = start; i < limit; i++) { + double x0 = inputs[i]; + y1 = (g * (y1 - x0)) + x1; + x1 = x0; + outputs[i] = y1; + } + + } +} diff --git a/src/main/java/com/jsyn/unitgen/FilterBandPass.java b/src/main/java/com/jsyn/unitgen/FilterBandPass.java new file mode 100644 index 0000000..b103400 --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/FilterBandPass.java @@ -0,0 +1,44 @@ +/* + * 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. + */ +/** + * Aug 21, 2009 + * com.jsyn.engine.units.Filter_HighPass.java + */ + +package com.jsyn.unitgen; + +/** + * Filter that allows frequencies around the center frequency to pass and blocks others. This filter + * is based on the BiQuad filter. Coefficients are updated whenever the frequency or Q changes. + * + * @author Phil Burk (C) 2009 Mobileer Inc Translated from 'C' to Java by Lisa + * Tolentino. + */ +public class FilterBandPass extends FilterBiquadCommon { + /** + * This method is by Filter_Biquad to update coefficients for the Filter_BandPass filter. + */ + @Override + public void updateCoefficients() { + double scalar = 1.0 / (1.0 + alpha); + + a0 = alpha * scalar; + a1 = 0.0; + a2 = -a0; + b1 = -2.0 * cos_omega * scalar; + b2 = (1.0 - alpha) * scalar; + } +} diff --git a/src/main/java/com/jsyn/unitgen/FilterBandStop.java b/src/main/java/com/jsyn/unitgen/FilterBandStop.java new file mode 100644 index 0000000..d4f5249 --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/FilterBandStop.java @@ -0,0 +1,49 @@ +/* + * 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.unitgen; + +/** + * Filter that blocks frequencies around the center frequency. This filter is based on the BiQuad + * filter. Coefficients are updated whenever the frequency or Q changes. + * + * @author Phil Burk (C) 2009 Mobileer Inc Translated from 'C' to Java by Lisa + * Tolentino. + */ +public class FilterBandStop extends FilterBiquadCommon { + + @Override + public void updateCoefficients() { + + // scalar = 1.0f / (1.0f + BQCM.alpha); + // A1_B1_Value = -2.0f * BQCM.cos_omega * scalar; + // + // csFilter->csFBQ_A0 = scalar; + // csFilter->csFBQ_A1 = A1_B1_Value; + // csFilter->csFBQ_A2 = scalar; + // csFilter->csFBQ_B1 = A1_B1_Value; + // csFilter->csFBQ_B2 = (1.0f - BQCM.alpha) * scalar; + + double scalar = 1.0 / (1.0 + alpha); + double a1_b1_value = -2.0 * cos_omega * scalar; + + this.a0 = scalar; + this.a1 = a1_b1_value; + this.a2 = scalar; + this.b1 = a1_b1_value; + this.b2 = (1.0 - alpha) * scalar; + } +} diff --git a/src/main/java/com/jsyn/unitgen/FilterBiquad.java b/src/main/java/com/jsyn/unitgen/FilterBiquad.java new file mode 100644 index 0000000..f9b792f --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/FilterBiquad.java @@ -0,0 +1,156 @@ +/* + * Copyright 2011 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.unitgen; + +import com.jsyn.ports.UnitInputPort; + +/** + * Base class for a set of IIR filters. + * + * @author Phil Burk (C) 2011 Mobileer Inc + * @see FilterBandStop + * @see FilterBandPass + * @see FilterLowPass + * @see FilterHighPass + * @see FilterTwoPolesTwoZeros + */ +public abstract class FilterBiquad extends TunableFilter { + public UnitInputPort amplitude; + + protected static final double MINIMUM_FREQUENCY = 0.00001; + protected static final double MINIMUM_GAIN = 0.00001; + protected static final double RATIO_MINIMUM = 0.499; + protected double a0; + protected double a1; + protected double a2; + protected double b1; + protected double b2; + private double x1; + private double x2; + private double y1; + private double y2; + protected double previousFrequency; + protected double omega; + protected double sin_omega; + protected double cos_omega; + + public FilterBiquad() { + addPort(amplitude = new UnitInputPort("Amplitude", 1.0)); + } + + /** + * Generic generate(int start, int limit) method calls this filter's recalculate() and + * performBiquadFilter(int, int) methods. + */ + @Override + public void generate(int start, int limit) { + recalculate(); + performBiquadFilter(start, limit); + } + + protected abstract void recalculate(); + + /** + * Each filter calls performBiquadFilter() through the generate(int, int) method. This method + * has converted Robert Bristow-Johnson's coefficients for the Direct I form in this way: Here + * is the equation that JSyn uses for this filter: + * + *
+     * y(n) = A0*x(n) + A1*x(n-1) + A2*x(n-2) -vB1*y(n-1) - B2*y(n-2)
+     * 
+ * + * Here is the equation that Robert Bristow-Johnson uses: + * + *
+     * y[n] = (b0/a0)*x[n] + (b1/a0)*x[n-1] + (b2/a0)*x[n-2] - (a1/a0)*y[n-1] - (a2/a0)*y[n-2]
+     * 
+ * + * So to translate between JSyn coefficients and RBJ coefficients: + * + *
+     * JSyn => RBJ
+     * A0 => b0/a0
+     * A1 => b1/a0
+     * A2 => b2/a0
+     * B1 => a1/a0
+     * B2 => a2/a0
+     * 
+ * + * @param start + * @param limit + */ + public void performBiquadFilter(int start, int limit) { + double[] inputs = input.getValues(); + double[] amplitudes = amplitude.getValues(); + double[] outputs = output.getValues(); + double a0_jsyn, a1_jsyn, a2_jsyn, b1_jsyn, b2_jsyn; + double x0_jsyn, x1_jsyn, x2_jsyn, y1_jsyn, y2_jsyn; + + x1_jsyn = this.x1; + x2_jsyn = this.x2; + + y1_jsyn = this.y1; + y2_jsyn = this.y2; + + a0_jsyn = this.a0; + a1_jsyn = this.a1; + a2_jsyn = this.a2; + + b1_jsyn = this.b1; + b2_jsyn = this.b2; + + // Permute filter operations to reduce data movement. + for (int i = start; i < limit; i += 2) + + { + x0_jsyn = inputs[i]; + y2_jsyn = (a0_jsyn * x0_jsyn) + (a1_jsyn * x1_jsyn) + (a2_jsyn * x2_jsyn) + - (b1_jsyn * y1_jsyn) - (b2_jsyn * y2_jsyn); + + outputs[i] = amplitudes[i] * y2_jsyn; + + x2_jsyn = inputs[i + 1]; + y1_jsyn = (a0_jsyn * x2_jsyn) + (a1_jsyn * x0_jsyn) + (a2_jsyn * x1_jsyn) + - (b1_jsyn * y2_jsyn) - (b2_jsyn * y1_jsyn); + + outputs[i + 1] = amplitudes[i + 1] * y1_jsyn; + + x1_jsyn = x2_jsyn; + x2_jsyn = x0_jsyn; + } + + this.x1 = x1_jsyn; // save filter state for next time + this.x2 = x2_jsyn; + + // apply small bipolar impulse to prevent arithmetic underflow + this.y1 = y1_jsyn + VERY_SMALL_FLOAT; + this.y2 = y2_jsyn - VERY_SMALL_FLOAT; + } + + protected void calculateOmega(double ratio) { + if (ratio >= FilterBiquad.RATIO_MINIMUM) // keep a minimum + // distance from Nyquist + { + ratio = FilterBiquad.RATIO_MINIMUM; + } + + omega = 2.0 * Math.PI * ratio; + cos_omega = Math.cos(omega); // compute cosine + sin_omega = Math.sin(omega); // compute sine + } + +} diff --git a/src/main/java/com/jsyn/unitgen/FilterBiquadCommon.java b/src/main/java/com/jsyn/unitgen/FilterBiquadCommon.java new file mode 100644 index 0000000..d84b39a --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/FilterBiquadCommon.java @@ -0,0 +1,99 @@ +/* + * 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.unitgen; + +import com.jsyn.ports.UnitInputPort; + +/** + * Extend this class to create a filter that implements a Biquad filter with a Q port. + * + * @author Phil Burk (C) 2009 Mobileer Inc Translated from 'C' to Java by Lisa + * Tolentino. + */ +public abstract class FilterBiquadCommon extends FilterBiquad { + public UnitInputPort Q; + + protected final static double MINIMUM_Q = 0.00001; + private double previousQ; + protected double alpha; + + /** + * No-argument constructor instantiates the Biquad common and adds a Q port to this filter. + */ + public FilterBiquadCommon() { + addPort(Q = new UnitInputPort("Q")); + Q.setup(0.1, 1.0, 10.0); + } + + /** + * Calculate coefficients based on the filter type, eg. LowPass. + */ + public abstract void updateCoefficients(); + + public void computeBiquadCommon(double ratio, double Q) { + if (ratio >= FilterBiquad.RATIO_MINIMUM) // keep a minimum distance + // from Nyquist + { + ratio = FilterBiquad.RATIO_MINIMUM; + } + + omega = 2.0 * Math.PI * ratio; + cos_omega = Math.cos(omega); // compute cosine + sin_omega = Math.sin(omega); // compute sine + alpha = sin_omega / (2.0 * Q); // set alpha + // LOGGER.debug("Q = " + Q + ", omega = " + omega + + // ", cos(omega) = " + cos_omega + ", alpha = " + alpha ); + } + + /** + * The recalculate() method checks and ensures that the frequency and Q values are at a minimum. + * It also only updates the Biquad coefficients if either frequency or Q have changed. + */ + @Override + public void recalculate() { + double frequencyValue = frequency.getValues()[0]; // grab frequency + // element (we'll + // only use + // element[0]) + double qValue = Q.getValues()[0]; // grab Q element (we'll only use + // element[0]) + + if (frequencyValue < MINIMUM_FREQUENCY) // ensure a minimum frequency + { + frequencyValue = MINIMUM_FREQUENCY; + } + + if (qValue < MINIMUM_Q) // ensure a minimum Q + { + qValue = MINIMUM_Q; + } + // only update changed values + if (isRecalculationNeeded(frequencyValue, qValue)) { + previousFrequency = frequencyValue; // hold previous frequency + previousQ = qValue; // hold previous Q + + double ratio = frequencyValue * getFramePeriod(); + computeBiquadCommon(ratio, qValue); + updateCoefficients(); + } + } + + protected boolean isRecalculationNeeded(double frequencyValue, double qValue) { + return (frequencyValue != previousFrequency) || (qValue != previousQ); + } + +} diff --git a/src/main/java/com/jsyn/unitgen/FilterBiquadShelf.java b/src/main/java/com/jsyn/unitgen/FilterBiquadShelf.java new file mode 100644 index 0000000..737d18d --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/FilterBiquadShelf.java @@ -0,0 +1,111 @@ +/* + * 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.unitgen; + +import com.jsyn.ports.UnitInputPort; + +/** + * This filter is based on the BiQuad filter and is used as a base class for FilterLowShelf and + * FilterHighShelf. Coefficients are updated whenever the frequency, gain or slope changes. + * + * @author Phil Burk (C) 2009 Mobileer Inc + */ +public abstract class FilterBiquadShelf extends FilterBiquad { + protected final static double MINIMUM_SLOPE = 0.00001; + + /** + * Gain of peak. Use 1.0 for flat response. + */ + public UnitInputPort gain; + + /** + * Shelf Slope parameter. When S = 1, the shelf slope is as steep as you can get it and remain + * monotonically increasing or decreasing gain with frequency. + */ + public UnitInputPort slope; + + private double prevGain; + private double prevSlope; + + private double beta; + protected double alpha; + protected double factorA; + protected double AP1; + protected double AM1; + protected double beta_sn; + protected double AP1cs; + protected double AM1cs; + + public FilterBiquadShelf() { + addPort(gain = new UnitInputPort("Gain", 1.0)); + addPort(slope = new UnitInputPort("Slope", 1.0)); + } + + /** + * Abstract method. Each filter must implement its update of coefficients. + */ + public abstract void updateCoefficients(); + + /** + * Compute coefficients for shelf filter if frequency, gain or slope have changed. + */ + @Override + public void recalculate() { + // Just look at first value to save time. + double frequencyValue = frequency.getValues()[0]; + if (frequencyValue < MINIMUM_FREQUENCY) { + frequencyValue = MINIMUM_FREQUENCY; + } + + double gainValue = gain.getValues()[0]; + if (gainValue < MINIMUM_GAIN) { + gainValue = MINIMUM_GAIN; + } + + double slopeValue = slope.getValues()[0]; + if (slopeValue < MINIMUM_SLOPE) { + slopeValue = MINIMUM_SLOPE; + } + + // Only do complex calculations if input changed. + if ((frequencyValue != previousFrequency) || (gainValue != prevGain) + || (slopeValue != prevSlope)) { + previousFrequency = frequencyValue; // hold previous frequency + prevGain = gainValue; + prevSlope = slopeValue; + + double ratio = frequencyValue * getFramePeriod(); + calculateOmega(ratio); + + factorA = Math.sqrt(gainValue); + + AP1 = factorA + 1.0; + AM1 = factorA - 1.0; + + /* Avoid sqrt(r<0) which hangs filter. */ + double beta2 = ((gainValue + 1.0) / slopeValue) - (AM1 * AM1); + beta = (beta2 < 0.0) ? 0.0 : Math.sqrt(beta2); + + beta_sn = beta * sin_omega; + AP1cs = AP1 * cos_omega; + AM1cs = AM1 * cos_omega; + + updateCoefficients(); + } + } + +} diff --git a/src/main/java/com/jsyn/unitgen/FilterFourPoles.java b/src/main/java/com/jsyn/unitgen/FilterFourPoles.java new file mode 100644 index 0000000..39a47c7 --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/FilterFourPoles.java @@ -0,0 +1,185 @@ +/* + * Copyright 2014 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.unitgen; + +import com.jsyn.ports.UnitInputPort; + +/** + * Resonant filter in the style of the Moog ladder filter. This implementation is loosely based on: + * http://www.musicdsp.org/archive.php?classid=3#26 + * More interesting reading: + * http://dafx04.na.infn.it/WebProc/Proc/P_061.pdf + * http://www.acoustics.ed.ac.uk/wp-content/uploads/AMT_MSc_FinalProjects + * /2012__Daly__AMT_MSc_FinalProject_MoogVCF.pdf + * http://www.music.mcgill.ca/~ich/research/misc/papers/cr1071.pdf + * + * @author Phil Burk (C) 2014 Mobileer Inc + * @see FilterLowPass + */ +public class FilterFourPoles extends TunableFilter { + public UnitInputPort Q; + public UnitInputPort gain; + + private static final double MINIMUM_FREQUENCY = 1.0; // blows up if near 0.01 + private static final double MINIMUM_Q = 0.00001; + + //private static final double SATURATION_COEFFICIENT = 0.1666667; + private static final double SATURATION_COEFFICIENT = 0.2; + // Inflection point where slope is zero. + private static final double SATURATION_UPPER_INPUT = 1.0 / Math.sqrt(3.0 * SATURATION_COEFFICIENT); + private static final double SATURATION_LOWER_INPUT = 0.0 - SATURATION_UPPER_INPUT; + private static final double SATURATION_UPPER_OUTPUT = cubicPolynomial(SATURATION_UPPER_INPUT); + private static final double SATURATION_LOWER_OUTPUT = cubicPolynomial(SATURATION_LOWER_INPUT); + + private double x1; + private double x2; + private double x3; + private double x4; + private double y1; + private double y2; + private double y3; + private double y4; + + private double previousFrequency; + private double previousQ; + // filter coefficients + private double f; + private double fTo4th; + private double feedback; + + private boolean oversampled = true; + + public FilterFourPoles() { + addPort(Q = new UnitInputPort("Q")); + frequency.setup(40.0, DEFAULT_FREQUENCY, 4000.0); + Q.setup(0.1, 2.0, 10.0); + } + + /** + * The recalculate() method checks and ensures that the frequency and Q values are at a minimum. + * It also only updates the coefficients if either frequency or Q have changed. + */ + public void recalculate() { + double frequencyValue = frequency.getValues()[0]; + double qValue = Q.getValues()[0]; + + if (frequencyValue < MINIMUM_FREQUENCY) // ensure a minimum frequency + { + frequencyValue = MINIMUM_FREQUENCY; + } + if (qValue < MINIMUM_Q) // ensure a minimum Q + { + qValue = MINIMUM_Q; + } + + // Only recompute coefficients if changed. + if ((frequencyValue != previousFrequency) || (qValue != previousQ)) { + previousFrequency = frequencyValue; + previousQ = qValue; + computeCoefficients(); + } + } + + private void computeCoefficients() { + double normalizedFrequency = previousFrequency * getFramePeriod(); + double fudge = 4.9 - 0.27 * previousQ; + if (fudge < 3.0) + fudge = 3.0; + f = normalizedFrequency * (oversampled ? 1.0 : 2.0) * fudge; + + double fSquared = f * f; + fTo4th = fSquared * fSquared; + feedback = 0.5 * previousQ * (1.0 - 0.15 * fSquared); + } + + @Override + public void generate(int start, int limit) { + double[] inputs = input.getValues(); + double[] outputs = output.getValues(); + + recalculate(); + + for (int i = start; i < limit; i++) { + double x0 = inputs[i]; + + if (oversampled) { + oneSample(0.0); + } + oneSample(x0); + outputs[i] = y4; + } + + // apply small bipolar impulse to prevent arithmetic underflow + y1 += VERY_SMALL_FLOAT; + y2 -= VERY_SMALL_FLOAT; + } + + private void oneSample(double x0) { + final double coeff = 0.3; + x0 -= y4 * feedback; // feedback + x0 *= 0.35013 * fTo4th; + y1 = x0 + coeff * x1 + (1 - f) * y1; // pole 1 + x1 = x0; + y2 = y1 + coeff * x2 + (1 - f) * y2; // pole 2 + x2 = y1; + y3 = y2 + coeff * x3 + (1 - f) * y3; // pole 3 + x3 = y2; + y4 = y3 + coeff * x4 + (1 - f) * y4; // pole 4 + y4 = clip(y4); + x4 = y3; + } + + public boolean isOversampled() { + return oversampled; + } + + public void setOversampled(boolean oversampled) { + this.oversampled = oversampled; + } + + // Soft saturation. This used to blow up the filter! + private static double cubicPolynomial(double x) { + return x - (x * x * x * SATURATION_COEFFICIENT); + } + + private static double clip(double x) { + if (x > SATURATION_UPPER_INPUT) { + return SATURATION_UPPER_OUTPUT; + } else if (x < SATURATION_LOWER_INPUT) { + return SATURATION_LOWER_OUTPUT; + } else { + return cubicPolynomial(x); + } + } + + public void reset() { + x1 = 0.0; + x2 = 0.0; + x3 = 0.0; + x4 = 0.0; + y1 = 0.0; + y2 = 0.0; + y3 = 0.0; + y4 = 0.0; + + previousFrequency = 0.0; + previousQ = 0.0; + f = 0.0; + fTo4th = 0.0; + feedback = 0.0; + } +} diff --git a/src/main/java/com/jsyn/unitgen/FilterHighPass.java b/src/main/java/com/jsyn/unitgen/FilterHighPass.java new file mode 100644 index 0000000..76ad6b9 --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/FilterHighPass.java @@ -0,0 +1,46 @@ +/* + * 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. + */ +/** + * Aug 21, 2009 + * com.jsyn.engine.units.Filter_HighPass.java + */ + +package com.jsyn.unitgen; + +/** + * Filter that allows frequencies above the center frequency to pass. This filter is based on the + * BiQuad filter. Coefficients are updated whenever the frequency or Q changes. + * + * @author Phil Burk (C) 2009 Mobileer Inc Translated from 'C' to Java by Lisa + * Tolentino. + */ +public class FilterHighPass extends FilterBiquadCommon { + /** + * This method is used by Filter_Biquad to update coefficients for the Filter_HighPass filter. + */ + @Override + public void updateCoefficients() { + double scalar = 1.0 / (1.0 + alpha); + double onePlusCosine = 1.0 + cos_omega; + double a0_a2_value = onePlusCosine * 0.5 * scalar; + + this.a0 = a0_a2_value; + this.a1 = -onePlusCosine * scalar; + this.a2 = a0_a2_value; + this.b1 = -2.0 * cos_omega * scalar; + this.b2 = (1.0 - alpha) * scalar; + } +} diff --git a/src/main/java/com/jsyn/unitgen/FilterHighShelf.java b/src/main/java/com/jsyn/unitgen/FilterHighShelf.java new file mode 100644 index 0000000..449090a --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/FilterHighShelf.java @@ -0,0 +1,38 @@ +/* + * 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.unitgen; + +/** + * HighShelf Filter. This creates a flat response above the cutoff frequency. This filter is + * sometimes used at the end of a bank of EQ filters. + * + * @author Phil Burk (C) 2009 Mobileer Inc + */ +public class FilterHighShelf extends FilterBiquadShelf { + /** + * This method is called by by Filter_BiquadShelf to update coefficients. + */ + @Override + public void updateCoefficients() { + double scalar = 1.0 / (AP1 - AM1cs + beta_sn); + a0 = factorA * (AP1 + AM1cs + beta_sn) * scalar; + a1 = -2.0 * factorA * (AM1 + AP1cs) * scalar; + a2 = factorA * (AP1 + AM1cs - beta_sn) * scalar; + b1 = 2.0 * (AM1 - AP1cs) * scalar; + b2 = (AP1 - AM1cs - beta_sn) * scalar; + } +} diff --git a/src/main/java/com/jsyn/unitgen/FilterLowPass.java b/src/main/java/com/jsyn/unitgen/FilterLowPass.java new file mode 100644 index 0000000..1557367 --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/FilterLowPass.java @@ -0,0 +1,65 @@ +/* + * 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. + */ +/** + * Aug 21, 2009 + * com.jsyn.engine.units.Filter_HighPass.java + */ + +package com.jsyn.unitgen; + +/** + * Filter that allows frequencies below the center frequency to pass. This filter is based on the + * BiQuad filter. Coefficients are updated whenever the frequency or Q changes. + * + * @author Phil Burk (C) 2009 Mobileer Inc Translated from 'C' to Java by Lisa + * Tolentino. + * + * @see FilterFourPoles + */ +public class FilterLowPass extends FilterBiquadCommon { + + /** + * This method is by FilterBiquad to update coefficients for the lowpass filter. + */ + @Override + public void updateCoefficients() { + + // scalar = 1.0f / (1.0f + BQCM.alpha); + // omc = (1.0f - BQCM.cos_omega); + // A0_A2_Value = omc * 0.5f * scalar; + // // translating from RBJ coefficients + // // A0 = (b0/(2*a0) + // // = ((1 - cos_omega)/2) / (1 + alpha) + // // = (omc*0.5) / (1 + alpha) + // // = (omc*0.5) * (1.0/(1 + alpha)) + // // = omc * 0.5 * scalar + // csFilter->csFBQ_A0 = A0_A2_Value; + // csFilter->csFBQ_A1 = omc * scalar; + // csFilter->csFBQ_A2 = A0_A2_Value; + // csFilter->csFBQ_B1 = -2.0f * BQCM.cos_omega * scalar; + // csFilter->csFBQ_B2 = (1.0f - BQCM.alpha) * scalar; + + double scalar = 1.0 / (1.0 + alpha); + double oneMinusCosine = 1.0 - cos_omega; + double a0_a2_value = oneMinusCosine * 0.5 * scalar; + + this.a0 = a0_a2_value; + this.a1 = oneMinusCosine * scalar; + this.a2 = a0_a2_value; + this.b1 = -2.0 * cos_omega * scalar; + this.b2 = (1.0 - alpha) * scalar; + } +} diff --git a/src/main/java/com/jsyn/unitgen/FilterLowShelf.java b/src/main/java/com/jsyn/unitgen/FilterLowShelf.java new file mode 100644 index 0000000..cf41f45 --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/FilterLowShelf.java @@ -0,0 +1,40 @@ +/* + * 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.unitgen; + +/** + * LowShelf Filter. This creates a flat response below the cutoff frequency. This filter is + * sometimes used at the end of a bank of EQ filters. This filter is based on the BiQuad filter. + * Coefficients are updated whenever the frequency or Q changes. + * + * @author Phil Burk (C) 2009 Mobileer Inc + */ +public class FilterLowShelf extends FilterBiquadShelf { + + /** + * This method is called by Filter_BiquadShelf to update coefficients. + */ + @Override + public void updateCoefficients() { + double scalar = 1.0 / (AP1 + AM1cs + beta_sn); + a0 = factorA * (AP1 - AM1cs + beta_sn) * scalar; + a1 = 2.0 * factorA * (AM1 - AP1cs) * scalar; + a2 = factorA * (AP1 - AM1cs - beta_sn) * scalar; + b1 = -2.0 * (AM1 + AP1cs) * scalar; + b2 = (AP1 + AM1cs - beta_sn) * scalar; + } +} diff --git a/src/main/java/com/jsyn/unitgen/FilterOnePole.java b/src/main/java/com/jsyn/unitgen/FilterOnePole.java new file mode 100644 index 0000000..090e42b --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/FilterOnePole.java @@ -0,0 +1,62 @@ +/* + * 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.unitgen; + +import com.jsyn.ports.UnitVariablePort; + +/** + * First Order, One Pole filter using the following formula: + * + *
+ * y(n) = A0 * x(n) - B1 * y(n - 1)
+ * 
+ * + * where y(n) is Output, x(n) is Input and y(n-1) is a delayed copy of the output. This filter is a + * recursive IIR or Infinite Impulse Response filter. It can be unstable depending on the values of + * the coefficients. This can be useful as a low-pass filter, or a "leaky integrator". A thorough + * description of the digital filter theory needed to fully describe this filter is beyond the scope + * of this document. Calculating coefficients is non-intuitive; the interested user is referred to + * one of the standard texts on filter theory (e.g., Moore, "Elements of Computer Music", section + * 2.4). + * + * @author Phil Burk (C) 2009 Mobileer Inc + * @see FilterLowPass + */ +public class FilterOnePole extends UnitFilter { + public UnitVariablePort a0; + public UnitVariablePort b1; + private double y1; + + public FilterOnePole() { + addPort(a0 = new UnitVariablePort("A0", 0.6)); + addPort(b1 = new UnitVariablePort("B1", -0.3)); + } + + @Override + public void generate(int start, int limit) { + double[] inputs = input.getValues(); + double[] outputs = output.getValues(); + double a0v = a0.getValue(); + double b1v = b1.getValue(); + + for (int i = start; i < limit; i++) { + double x0 = inputs[i]; + outputs[i] = y1 = (a0v * x0) - (b1v * y1); + } + + } +} diff --git a/src/main/java/com/jsyn/unitgen/FilterOnePoleOneZero.java b/src/main/java/com/jsyn/unitgen/FilterOnePoleOneZero.java new file mode 100644 index 0000000..ed1868c --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/FilterOnePoleOneZero.java @@ -0,0 +1,68 @@ +/* + * Copyright 1997 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.unitgen; + +import com.jsyn.ports.UnitVariablePort; + +/** + * First Order, One Pole, One Zero filter using the following formula: + * + *
+ * y(n) = A0 * x(n) + A1 * x(n - 1) - B1 * y(n - 1)
+ * 
+ * + * where y(n) is Output, x(n) is Input, x(n-1) is a delayed copy of the input, and y(n-1) is a + * delayed copy of the output. This filter is a recursive IIR or Infinite Impulse Response filter. + * it can be unstable depending on the values of the coefficients. A thorough description of the + * digital filter theory needed to fully describe this filter is beyond the scope of this document. + * Calculating coefficients is non-intuitive; the interested user is referred to one of the standard + * texts on filter theory (e.g., Moore, "Elements of Computer Music", section 2.4). + * + * @author (C) 1997-2009 Phil Burk, SoftSynth.com + * @see FilterLowPass + */ + +public class FilterOnePoleOneZero extends UnitFilter { + public UnitVariablePort a0; + public UnitVariablePort a1; + public UnitVariablePort b1; + + private double x1; + private double y1; + + public FilterOnePoleOneZero() { + addPort(a0 = new UnitVariablePort("A0")); + addPort(a1 = new UnitVariablePort("A1")); + addPort(b1 = new UnitVariablePort("B1")); + } + + @Override + public void generate(int start, int limit) { + double[] inputs = input.getValues(); + double[] outputs = output.getValues(); + double a0v = a0.getValue(); + double a1v = a1.getValue(); + double b1v = b1.getValue(); + + for (int i = start; i < limit; i++) { + double x0 = inputs[i]; + outputs[i] = y1 = (a0v * x0) + (a1v * x1) + (b1v * y1); + x1 = x0; + } + + } +} diff --git a/src/main/java/com/jsyn/unitgen/FilterOneZero.java b/src/main/java/com/jsyn/unitgen/FilterOneZero.java new file mode 100644 index 0000000..2a07a16 --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/FilterOneZero.java @@ -0,0 +1,65 @@ +/* + * Copyright 2011 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.unitgen; + +import com.jsyn.ports.UnitVariablePort; + +/** + * First Order, One Zero filter using the following formula: + * + *
+ * y(n) = A0 * x(n) + A1 * x(n - 1)
+ * 
+ * + * where y(n) is Output, x(n) is Input and x(n-1) is Input at the prior sample tick. Setting A1 + * positive gives a low-pass response; setting A1 negative gives a high-pass response. The bandwidth + * of this filter is fairly high, so it often serves a building block by being cascaded with other + * filters. If A0 and A1 are both 0.5, then this filter is a simple averaging lowpass filter, with a + * zero at SR/2 = 22050 Hz. If A0 is 0.5 and A1 is -0.5, then this filter is a high pass filter, + * with a zero at 0.0 Hz. A thorough description of the digital filter theory needed to fully + * describe this filter is beyond the scope of this document. Calculating coefficients is + * non-intuitive; the interested user is referred to one of the standard texts on filter theory + * (e.g., Moore, "Elements of Computer Music", section 2.4). + * + * @author Phil Burk (C) 2011 Mobileer Inc + * @see FilterLowPass + */ +public class FilterOneZero extends UnitFilter { + public UnitVariablePort a0; + public UnitVariablePort a1; + private double x1; + + public FilterOneZero() { + addPort(a0 = new UnitVariablePort("A0", 0.5)); + addPort(a1 = new UnitVariablePort("A1", 0.5)); + } + + @Override + public void generate(int start, int limit) { + double[] inputs = input.getValues(); + double[] outputs = output.getValues(); + double a0v = a0.getValue(); + double a1v = a1.getValue(); + + for (int i = start; i < limit; i++) { + double x0 = inputs[i]; + outputs[i] = (a0v * x0) + (a1v * x1); + x1 = x0; + } + + } +} diff --git a/src/main/java/com/jsyn/unitgen/FilterPeakingEQ.java b/src/main/java/com/jsyn/unitgen/FilterPeakingEQ.java new file mode 100644 index 0000000..bec7096 --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/FilterPeakingEQ.java @@ -0,0 +1,68 @@ +/* + * 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.unitgen; + +import com.jsyn.ports.UnitInputPort; + +/** + * PeakingEQ Filter. This can be used to raise or lower the gain around the cutoff frequency. This + * filter is sometimes used in the middle of a bank of EQ filters. This filter is based on the + * BiQuad filter. Coefficients are updated whenever the frequency or Q changes. + * + * @author Phil Burk (C) 2009 Mobileer Inc + */ +public class FilterPeakingEQ extends FilterBiquadCommon { + public UnitInputPort gain; + + private double previousGain; + + public FilterPeakingEQ() { + addPort(gain = new UnitInputPort("Gain", 1.0)); + } + + @Override + protected boolean isRecalculationNeeded(double frequencyValue, double qValue) { + double currentGain = gain.getValues()[0]; + if (currentGain < MINIMUM_GAIN) { + currentGain = MINIMUM_GAIN; + } + + boolean needed = super.isRecalculationNeeded(frequencyValue, qValue); + needed |= (previousGain != currentGain); + + previousGain = currentGain; + return needed; + } + + @Override + public void updateCoefficients() { + double factorA = Math.sqrt(previousGain); + double alphaTimesA = alpha * factorA; + double alphaOverA = alpha / factorA; + // Note this is not the normal scalar! + double scalar = 1.0 / (1.0 + alphaOverA); + double a1_b1_value = -2.0 * cos_omega * scalar; + + this.a0 = (1.0 + alphaTimesA) * scalar; + + this.a1 = a1_b1_value; + this.a2 = (1.0 - alphaTimesA) * scalar; + + this.b1 = a1_b1_value; + this.b2 = (1.0 - alphaOverA) * scalar; + } +} diff --git a/src/main/java/com/jsyn/unitgen/FilterStateVariable.java b/src/main/java/com/jsyn/unitgen/FilterStateVariable.java new file mode 100644 index 0000000..3a0f05c --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/FilterStateVariable.java @@ -0,0 +1,120 @@ +/* + * 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.unitgen; + +import com.jsyn.ports.UnitInputPort; +import com.jsyn.ports.UnitOutputPort; + +/** + * A versatile filter described in Hal Chamberlain's "Musical Applications of MicroProcessors". It + * is convenient because its frequency and resonance can each be controlled by a single value. The + * "output" port of this filter is the "lowPass" output multiplied by the "amplitude" + * + * @author Phil Burk (C) 2009 Mobileer Inc + * @see FilterLowPass + * @see FilterHighPass + * @see FilterFourPoles + */ +public class FilterStateVariable extends TunableFilter { + /** + * Amplitude of Output in the range of 0.0 to 1.0. SIGNAL_TYPE_RAW_SIGNED Defaults to 1.0 + *

+ * Note that the amplitude only affects the "output" port and not the lowPass, bandPass or + * highPass signals. Use a Multiply unit if you need to scale those signals. + */ + public UnitInputPort amplitude; + + /** + * Controls feedback that causes self oscillation. Actually 1/Q - SIGNAL_TYPE_RAW_SIGNED in the + * range of 0.0 to 1.0. Defaults to 0.125. + */ + public UnitInputPort resonance; + /** + * Low pass filtered signal. + *

+ * Note that this signal is not affected by the amplitude port. + */ + public UnitOutputPort lowPass; + /** + * Band pass filtered signal. + *

+ * Note that this signal is not affected by the amplitude port. + */ + public UnitOutputPort bandPass; + /** + * High pass filtered signal. + *

+ * Note that this signal is not affected by the amplitude port. + */ + public UnitOutputPort highPass; + + private double freqInternal; + private double previousFrequency = Double.MAX_VALUE; // So we trigger an immediate update. + private double lowPassValue; + private double bandPassValue; + + /** + * No-argument constructor instantiates the Biquad common and adds an amplitude port to this + * filter. + */ + public FilterStateVariable() { + frequency.set(440.0); + addPort(resonance = new UnitInputPort("Resonance", 0.2)); + addPort(amplitude = new UnitInputPort("Amplitude", 1.0)); + addPort(lowPass = new UnitOutputPort("LowPass")); + addPort(bandPass = new UnitOutputPort("BandPass")); + addPort(highPass = new UnitOutputPort("HighPass")); + } + + @Override + public void generate(int start, int limit) { + double[] inputs = input.getValues(); + double[] outputs = output.getValues(); + double[] frequencies = frequency.getValues(); + double[] amplitudes = amplitude.getValues(); + double[] reses = resonance.getValues(); + double[] lows = lowPass.getValues(); + double[] highs = highPass.getValues(); + double[] bands = bandPass.getValues(); + + double newFreq = frequencies[0]; + if (newFreq != previousFrequency) { + previousFrequency = newFreq; + freqInternal = 2.0 * Math.sin(Math.PI * newFreq * getFramePeriod()); + } + + for (int i = start; i < limit; i++) { + lowPassValue = (freqInternal * bandPassValue) + lowPassValue; + // Clip between -1 and +1 to prevent blowup. + lowPassValue = (lowPassValue < -1.0) ? -1.0 : ((lowPassValue > 1.0) ? 1.0 + : lowPassValue); + lows[i] = lowPassValue; + + outputs[i] = lowPassValue * (amplitudes[i]); + double highPassValue = inputs[i] - (reses[i] * bandPassValue) - lowPassValue; + // LOGGER.debug("low = " + lowPassValue + ", band = " + bandPassValue + + // ", high = " + highPassValue ); + highs[i] = highPassValue; + + bandPassValue = (freqInternal * highPassValue) + bandPassValue; + bands[i] = bandPassValue; + // LOGGER.debug("low = " + lowPassValue + ", band = " + bandPassValue + + // ", high = " + highPassValue ); + } + } + +} diff --git a/src/main/java/com/jsyn/unitgen/FilterTwoPoles.java b/src/main/java/com/jsyn/unitgen/FilterTwoPoles.java new file mode 100644 index 0000000..0c68a64 --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/FilterTwoPoles.java @@ -0,0 +1,66 @@ +/* + * Copyright 1997 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.unitgen; + +import com.jsyn.ports.UnitVariablePort; + +/** + * Second Order, Two Pole filter using the following formula: + * + *

+ * y(n) = A0 * x(n) - B1 * y(n - 1) - B2 * y(n - 2)
+ * 
+ * + * where y(n) is Output, x(n) is Input, and y(n-1) is a delayed copy of the output. This filter is a + * recursive IIR or Infinite Impulse Response filter. it can be unstable depending on the values of + * the coefficients. A thorough description of the digital filter theory needed to fully describe + * this filter is beyond the scope of this document. Calculating coefficients is non-intuitive; the + * interested user is referred to one of the standard texts on filter theory (e.g., Moore, + * "Elements of Computer Music", section 2.4). + * + * @author (C) 1997-2009 Phil Burk, Mobileer Inc + */ + +public class FilterTwoPoles extends UnitFilter { + public UnitVariablePort a0; + public UnitVariablePort b1; + public UnitVariablePort b2; + private double y1; + private double y2; + + public FilterTwoPoles() { + addPort(a0 = new UnitVariablePort("A0")); + addPort(b1 = new UnitVariablePort("B1")); + addPort(b2 = new UnitVariablePort("B2")); + } + + @Override + public void generate(int start, int limit) { + double[] inputs = input.getValues(); + double[] outputs = output.getValues(); + double a0v = a0.getValue(); + double b1v = b1.getValue(); + double b2v = b2.getValue(); + + for (int i = start; i < limit; i++) { + double x0 = inputs[i]; + outputs[i] = y1 = 2.0 * ((a0v * x0) + (b1v * y1) + (b2v * y2)); + y2 = y1; + } + + } +} diff --git a/src/main/java/com/jsyn/unitgen/FilterTwoPolesTwoZeros.java b/src/main/java/com/jsyn/unitgen/FilterTwoPolesTwoZeros.java new file mode 100644 index 0000000..cde279f --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/FilterTwoPolesTwoZeros.java @@ -0,0 +1,79 @@ +/* + * Copyright 1997 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.unitgen; + +import com.jsyn.ports.UnitVariablePort; + +/** + * Second Order, Two Pole, Two Zero filter using the following formula: + * + *
+ * y(n) = 2.0 * (A0 * x(n) + A1 * x(n - 1) + A2 * x(n - 2) - B1 * y(n - 1) - B2 * y(n - 2))
+ * 
+ * + * where y(n) is Output, x(n) is Input, x(n-1) is a delayed copy of the input, and y(n-1) is a + * delayed copy of the output. This filter is a recursive IIR or Infinite Impulse Response filter. + * It can be unstable depending on the values of the coefficients. This filter is basically the same + * as the FilterBiquad with different ports. A thorough description of the digital filter theory + * needed to fully describe this filter is beyond the scope of this document. Calculating + * coefficients is non-intuitive; the interested user is referred to one of the standard texts on + * filter theory (e.g., Moore, "Elements of Computer Music", section 2.4). Special thanks to Robert + * Bristow-Johnson for contributing his filter equations to the music-dsp list. They were used for + * calculating the coefficients for the lowPass, highPass, and other parametric filter calculations. + * + * @author (C) 1997-2009 Phil Burk, SoftSynth.com + */ + +public class FilterTwoPolesTwoZeros extends UnitFilter { + public UnitVariablePort a0; + public UnitVariablePort a1; + public UnitVariablePort a2; + public UnitVariablePort b1; + public UnitVariablePort b2; + private double x1; + private double y1; + private double x2; + private double y2; + + public FilterTwoPolesTwoZeros() { + addPort(a0 = new UnitVariablePort("A0")); + addPort(a1 = new UnitVariablePort("A1")); + addPort(a2 = new UnitVariablePort("A2")); + addPort(b1 = new UnitVariablePort("B1")); + addPort(b2 = new UnitVariablePort("B2")); + } + + @Override + public void generate(int start, int limit) { + double[] inputs = input.getValues(); + double[] outputs = output.getValues(); + double a0v = a0.getValue(); + double a1v = a1.getValue(); + double a2v = a2.getValue(); + double b1v = b1.getValue(); + double b2v = b2.getValue(); + + for (int i = start; i < limit; i++) { + double x0 = inputs[i]; + outputs[i] = y1 = 2.0 * ((a0v * x0) + (a1v * x1) + (a2v * x2) + (b1v * y1) + (b2v * y2)); + x2 = x1; + x1 = x0; + y2 = y1; + } + + } +} diff --git a/src/main/java/com/jsyn/unitgen/FixedRateMonoReader.java b/src/main/java/com/jsyn/unitgen/FixedRateMonoReader.java new file mode 100644 index 0000000..c6edc23 --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/FixedRateMonoReader.java @@ -0,0 +1,52 @@ +/* + * 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.unitgen; + +import com.jsyn.ports.UnitOutputPort; + +/** + * Simple sample player. Play one sample per audio frame with no interpolation. + * + * @author Phil Burk (C) 2009 Mobileer Inc + */ +public class FixedRateMonoReader extends SequentialDataReader { + + public FixedRateMonoReader() { + addPort(output = new UnitOutputPort()); + } + + @Override + public void generate(int start, int limit) { + + double[] amplitudes = amplitude.getValues(); + double[] outputs = output.getValues(); + + for (int i = start; i < limit; i++) { + if (dataQueue.hasMore()) { + double fdata = dataQueue.readNextMonoDouble(getFramePeriod()); + outputs[i] = fdata * amplitudes[i]; + } else { + outputs[i] = 0.0; + if (dataQueue.testAndClearAutoStop()) { + autoStop(); + } + } + dataQueue.firePendingCallbacks(); + } + } + +} diff --git a/src/main/java/com/jsyn/unitgen/FixedRateMonoWriter.java b/src/main/java/com/jsyn/unitgen/FixedRateMonoWriter.java new file mode 100644 index 0000000..c215c55 --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/FixedRateMonoWriter.java @@ -0,0 +1,54 @@ +/* + * 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.unitgen; + +import com.jsyn.ports.UnitInputPort; + +/** + * Simple sample writer. Write one sample per audio frame with no interpolation. This can be used to + * record audio or to build delay lines. + * + * Note that you must call start() on this unit because it does not have an output for pulling data. + * + * @see FixedRateStereoWriter + * @author Phil Burk (C) 2009 Mobileer Inc + */ +public class FixedRateMonoWriter extends SequentialDataWriter { + + public FixedRateMonoWriter() { + addPort(input = new UnitInputPort("Input")); + } + + @Override + public void generate(int start, int limit) { + double[] inputs = input.getValues(); + + for (int i = start; i < limit; i++) { + if (dataQueue.hasMore()) { + double value = inputs[i]; + dataQueue.writeNextDouble(value); + } else { + if (dataQueue.testAndClearAutoStop()) { + autoStop(); + } + } + dataQueue.firePendingCallbacks(); + } + + } + +} diff --git a/src/main/java/com/jsyn/unitgen/FixedRateStereoReader.java b/src/main/java/com/jsyn/unitgen/FixedRateStereoReader.java new file mode 100644 index 0000000..6dcf72c --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/FixedRateStereoReader.java @@ -0,0 +1,59 @@ +/* + * 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.unitgen; + +import com.jsyn.ports.UnitOutputPort; + +/** + * Simple stereo sample player. Play one sample per audio frame with no interpolation. + * + * @author Phil Burk (C) 2009 Mobileer Inc + */ +public class FixedRateStereoReader extends SequentialDataReader { + public FixedRateStereoReader() { + addPort(output = new UnitOutputPort(2, "Output")); + dataQueue.setNumChannels(2); + } + + @Override + public void generate(int start, int limit) { + double[] amplitudes = amplitude.getValues(); + double[] output0s = output.getValues(0); + double[] output1s = output.getValues(1); + + for (int i = start; i < limit; i++) { + if (dataQueue.hasMore()) { + dataQueue.beginFrame(getFramePeriod()); + double fdata = dataQueue.readCurrentChannelDouble(0); + // LOGGER.debug("SampleReader_16F2: left = " + fdata ); + double amp = amplitudes[i]; + output0s[i] = fdata * amp; + fdata = dataQueue.readCurrentChannelDouble(1); + // LOGGER.debug("SampleReader_16F2: right = " + fdata ); + output1s[i] = fdata * amp; + dataQueue.endFrame(); + } else { + output0s[i] = 0.0; + output1s[i] = 0.0; + if (dataQueue.testAndClearAutoStop()) { + autoStop(); + } + } + dataQueue.firePendingCallbacks(); + } + } +} diff --git a/src/main/java/com/jsyn/unitgen/FixedRateStereoWriter.java b/src/main/java/com/jsyn/unitgen/FixedRateStereoWriter.java new file mode 100644 index 0000000..e4502f9 --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/FixedRateStereoWriter.java @@ -0,0 +1,60 @@ +/* + * 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.unitgen; + +import com.jsyn.ports.UnitInputPort; + +/** + * Simple stereo sample writer. Write two samples per audio frame with no interpolation. This can be + * used to record audio or to build delay lines. + * + * Note that you must call start() on this unit because it does not have an output for pulling data. + * + * @see FixedRateMonoWriter + * @author Phil Burk (C) 2009 Mobileer Inc + */ +public class FixedRateStereoWriter extends SequentialDataWriter { + + public FixedRateStereoWriter() { + addPort(input = new UnitInputPort(2, "Input")); + dataQueue.setNumChannels(2); + } + + @Override + public void generate(int start, int limit) { + double[] input0s = input.getValues(0); + double[] input1s = input.getValues(1); + + for (int i = start; i < limit; i++) { + if (dataQueue.hasMore()) { + dataQueue.beginFrame(getFramePeriod()); + double value = input0s[i]; + dataQueue.writeCurrentChannelDouble(0, value); + value = input1s[i]; + dataQueue.writeCurrentChannelDouble(1, value); + dataQueue.endFrame(); + } else { + if (dataQueue.testAndClearAutoStop()) { + autoStop(); + } + } + dataQueue.firePendingCallbacks(); + } + + } + +} diff --git a/src/main/java/com/jsyn/unitgen/FourWayFade.java b/src/main/java/com/jsyn/unitgen/FourWayFade.java new file mode 100644 index 0000000..8f88965 --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/FourWayFade.java @@ -0,0 +1,94 @@ +/* + * Copyright 1997 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.unitgen; + +import com.jsyn.ports.UnitInputPort; +import com.jsyn.ports.UnitOutputPort; + +/** + * FourWayFade unit. + *

+ * Mix inputs 0-3 based on the value of two fade ports. You can think of the four inputs arranged + * clockwise as follows. + *

+ * + *
+ *      input[0] ---- input[1]
+ *        |             |
+ *        |             |
+ *        |             |
+ *      input[3] ---- input[2]
+ * 
+ * + * The "fade" port has two parts. Fade[0] fades between the pair of inputs (0,3) and the pair of + * inputs (1,2). Fade[1] fades between the pair of inputs (0,1) and the pair of inputs (3,2). + * + *
+ *    Fade[0]    Fade[1]    Output
+ *      -1         -1       Input[3]
+ *      -1         +1       Input[0]
+ *      +1         -1       Input[2]
+ *      +1         +1       Input[1]
+ *
+ *
+ *      -----Fade[0]----->
+ *
+ *         A
+ *         |
+ *         |
+ *      Fade[1]
+ *         |
+ *         |
+ * 
+ *

+ * + * @author (C) 1997-2009 Phil Burk, Mobileer Inc + */ +public class FourWayFade extends UnitGenerator { + public UnitInputPort input; + public UnitInputPort fade; + public UnitOutputPort output; + + /* Define Unit Ports used by connect() and set(). */ + public FourWayFade() { + addPort(input = new UnitInputPort(4, "Input")); + addPort(fade = new UnitInputPort(2, "Fade")); + addPort(output = new UnitOutputPort()); + } + + @Override + public void generate(int start, int limit) { + double[] inputAs = input.getValues(0); + double[] inputBs = input.getValues(1); + double[] inputCs = input.getValues(2); + double[] inputDs = input.getValues(3); + double[] fadeLRs = fade.getValues(0); + double[] fadeFBs = fade.getValues(1); + double[] outputs = output.getValues(0); + + for (int i = start; i < limit; i++) { + // Scale and offset to 0.0 to 1.0 range. + double gainLR = (fadeLRs[i] * 0.5) + 0.5; + double temp = 1.0 - gainLR; + double mixFront = (inputAs[i] * temp) + (inputBs[i] * gainLR); + double mixBack = (inputDs[i] * temp) + (inputCs[i] * gainLR); + + double gainFB = (fadeFBs[i] * 0.5) + 0.5; + outputs[i] = (mixBack * (1.0 - gainFB)) + (mixFront * gainFB); + } + } +} diff --git a/src/main/java/com/jsyn/unitgen/FunctionEvaluator.java b/src/main/java/com/jsyn/unitgen/FunctionEvaluator.java new file mode 100644 index 0000000..0cc0c83 --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/FunctionEvaluator.java @@ -0,0 +1,76 @@ +/* + * 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. + */ +/** + * Aug 26, 2009 + * com.jsyn.engine.units.TunableFilter.java + */ + +package com.jsyn.unitgen; + +import com.jsyn.data.Function; +import com.jsyn.ports.UnitFunctionPort; +import com.jsyn.ports.UnitInputPort; + +/** + * Convert an input value to an output value. The Function is typically implemented by looking up a + * value in a DoubleTable. But other implementations of Function can be used. Input typically ranges + * from -1.0 to +1.0. + * + *

+ * 
+ *     // A unit that will lookup the function.
+ * 	FunctionEvaluator shaper = new FunctionEvaluator();
+ * 	synth.add( shaper );
+ * 	shaper.start();
+ * 	// Define a custom function.
+ * 	Function cuber = new Function()
+ * 	{
+ * 		public double evaluate( double x )
+ * 		{
+ * 			return x * x * x;
+ * 		}
+ * 	};
+ * 	shaper.function.set(cuber);
+ * 
+ * 	shaper.input.set( 0.5 );
+ *  
+ * 
+ * + * @author Phil Burk (C) 2009 Mobileer Inc + * @see Function + */ +public class FunctionEvaluator extends UnitFilter { + public UnitInputPort amplitude; + public UnitFunctionPort function; + + public FunctionEvaluator() { + addPort(amplitude = new UnitInputPort("Amplitude", 1.0)); + addPort(function = new UnitFunctionPort("Function")); + } + + @Override + public void generate(int start, int limit) { + double[] inputs = input.getValues(); + double[] amplitudes = amplitude.getValues(); + double[] outputs = output.getValues(); + Function functionObject = function.get(); + + for (int i = start; i < limit; i++) { + outputs[i] = functionObject.evaluate(inputs[i]) * amplitudes[i]; + } + + } +} diff --git a/src/main/java/com/jsyn/unitgen/FunctionOscillator.java b/src/main/java/com/jsyn/unitgen/FunctionOscillator.java new file mode 100644 index 0000000..30d32d5 --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/FunctionOscillator.java @@ -0,0 +1,58 @@ +/* + * 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.unitgen; + +import com.jsyn.data.Function; +import com.jsyn.ports.UnitFunctionPort; + +/** + * Oscillator that uses a Function object to define the waveform. Note that a DoubleTable can be + * used as the Function. + * + * @author Phil Burk (C) 2009 Mobileer Inc + */ +public class FunctionOscillator extends UnitOscillator { + public UnitFunctionPort function; + + public FunctionOscillator() { + addPort(function = new UnitFunctionPort("Function")); + } + + @Override + public void generate(int start, int limit) { + double[] frequencies = frequency.getValues(); + double[] amplitudes = amplitude.getValues(); + double[] outputs = output.getValues(); + + Function functionObject = function.get(); + + // Variables have a single value. + double currentPhase = phase.getValue(); + + for (int i = start; i < limit; i++) { + // Generate sawtooth phasor to provide phase for function lookup. + double phaseIncrement = convertFrequencyToPhaseIncrement(frequencies[i]); + currentPhase = incrementWrapPhase(currentPhase, phaseIncrement); + double value = functionObject.evaluate(currentPhase); + outputs[i] = value * amplitudes[i]; + } + + // Value needs to be saved for next time. + phase.setValue(currentPhase); + } + +} diff --git a/src/main/java/com/jsyn/unitgen/Grain.java b/src/main/java/com/jsyn/unitgen/Grain.java new file mode 100644 index 0000000..812061c --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/Grain.java @@ -0,0 +1,89 @@ +/* + * Copyright 2011 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.unitgen; + +/** + * A single Grain that is normally created and controlled by a GrainFarm. + * + * @author Phil Burk (C) 2011 Mobileer Inc + */ +public class Grain implements GrainEnvelope { + private double frameRate; + private double amplitude = 1.0; + + private GrainSource source; + private GrainEnvelope envelope; + + public Grain(GrainSource source, GrainEnvelope envelope) { + this.source = source; + this.envelope = envelope; + } + + @Override + public double next() { + if (envelope.hasMoreValues()) { + double env = envelope.next(); + return source.next() * env * amplitude; + } else { + return 0.0; + } + } + + @Override + public boolean hasMoreValues() { + return envelope.hasMoreValues(); + } + + @Override + public void reset() { + source.reset(); + envelope.reset(); + } + + public void setRate(double rate) { + source.setRate(rate); + } + + @Override + public void setDuration(double duration) { + envelope.setDuration(duration); + } + + @Override + public double getFrameRate() { + return frameRate; + } + + @Override + public void setFrameRate(double frameRate) { + this.frameRate = frameRate; + source.setFrameRate(frameRate); + envelope.setFrameRate(frameRate); + } + + public double getAmplitude() { + return amplitude; + } + + public void setAmplitude(double amplitude) { + this.amplitude = amplitude; + } + + public GrainSource getSource() { + return source; + } +} diff --git a/src/main/java/com/jsyn/unitgen/GrainCommon.java b/src/main/java/com/jsyn/unitgen/GrainCommon.java new file mode 100644 index 0000000..a7a04fc --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/GrainCommon.java @@ -0,0 +1,32 @@ +/* + * 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.unitgen; + +public class GrainCommon { + protected double frameRate; + + public double getFrameRate() { + return frameRate; + } + + public void setFrameRate(double frameRate) { + this.frameRate = frameRate; + } + + public void reset() { + } +} diff --git a/src/main/java/com/jsyn/unitgen/GrainEnvelope.java b/src/main/java/com/jsyn/unitgen/GrainEnvelope.java new file mode 100644 index 0000000..e6ff24c --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/GrainEnvelope.java @@ -0,0 +1,52 @@ +/* + * Copyright 2011 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.unitgen; + +/** + * This envelope should start at 0.0, go up to 1.0 and then return to 0.0 in duration time. + * + * @author Phil Burk (C) 2011 Mobileer Inc + */ +public interface GrainEnvelope { + + double getFrameRate(); + + void setFrameRate(double frameRate); + + /** + * @return next amplitude value of envelope + */ + double next(); + + /** + * Are there any more values to be generated in the envelope? + * + * @return true if more + */ + boolean hasMoreValues(); + + /** + * Prepare to start a new envelope. + */ + void reset(); + + /** + * @param duration in seconds. + */ + void setDuration(double duration); + +} diff --git a/src/main/java/com/jsyn/unitgen/GrainFarm.java b/src/main/java/com/jsyn/unitgen/GrainFarm.java new file mode 100644 index 0000000..78179bc --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/GrainFarm.java @@ -0,0 +1,178 @@ +/* + * Copyright 2011 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.unitgen; + +import com.jsyn.ports.UnitInputPort; +import com.jsyn.ports.UnitOutputPort; +import com.jsyn.util.PseudoRandom; + +/** + * A unit generator that generates a cloud of sound using multiple Grains. Special thanks to my + * friend Ross Bencina for his excellent article on Granular Synthesis. Several of his ideas are + * reflected in this architecture. "Implementing Real-Time Granular Synthesis" by Ross Bencina, + * Audio Anecdotes III, 2001. + * + *

+   synth.add( sampleGrainFarm = new GrainFarm() );
+   grainFarm.allocate( NUM_GRAINS );
+
+ * + * @author Phil Burk (C) 2011 Mobileer Inc + * @see Grain + * @see GrainSourceSine + * @see RaisedCosineEnvelope + */ +public class GrainFarm extends UnitGenerator implements UnitSource { + /** A scaler for playback rate. Nominally 1.0. */ + public UnitInputPort rate; + public UnitInputPort rateRange; + public UnitInputPort amplitude; + public UnitInputPort amplitudeRange; + public UnitInputPort density; + public UnitInputPort duration; + public UnitInputPort durationRange; + public UnitOutputPort output; + + PseudoRandom randomizer; + private GrainState[] states; + private double countScaler = 1.0; + private final GrainScheduler scheduler = new StochasticGrainScheduler(); + + public GrainFarm() { + randomizer = new PseudoRandom(); + addPort(rate = new UnitInputPort("Rate", 1.0)); + addPort(amplitude = new UnitInputPort("Amplitude", 1.0)); + addPort(duration = new UnitInputPort("Duration", 0.01)); + addPort(rateRange = new UnitInputPort("RateRange", 0.0)); + addPort(amplitudeRange = new UnitInputPort("AmplitudeRange", 0.0)); + addPort(durationRange = new UnitInputPort("DurationRange", 0.0)); + addPort(density = new UnitInputPort("Density", 0.1)); + addPort(output = new UnitOutputPort()); + } + + private class GrainState { + Grain grain; + int countdown; + double lastDuration; + final static int STATE_IDLE = 0; + final static int STATE_GAP = 1; + final static int STATE_RUNNING = 2; + int state = STATE_IDLE; + private double gapError; + + public double next(int i) { + double output = 0.0; + if (state == STATE_RUNNING) { + if (grain.hasMoreValues()) { + output = grain.next(); + } else { + startGap(i); + } + } else if (state == STATE_GAP) { + if (countdown > 0) { + countdown -= 1; + } else if (countdown == 0) { + state = STATE_RUNNING; + grain.reset(); + + setupGrain(grain, i); + + double dur = nextDuration(i); + grain.setDuration(dur); + } + } else if (state == STATE_IDLE) { + nextDuration(i); // initialize lastDuration + startGap(i); + } + return output; + } + + private double nextDuration(int i) { + double dur = duration.getValues()[i]; + dur = scheduler.nextDuration(dur); + lastDuration = dur; + return dur; + } + + private void startGap(int i) { + state = STATE_GAP; + double dens = density.getValues()[i]; + double gap = scheduler.nextGap(lastDuration, dens) * getFrameRate(); + gap += gapError; + countdown = (int) gap; + gapError = gap - countdown; + } + } + + public void setGrainArray(Grain[] grains) { + countScaler = 1.0 / grains.length; + states = new GrainState[grains.length]; + for (int i = 0; i < states.length; i++) { + states[i] = new GrainState(); + states[i].grain = grains[i]; + grains[i].setFrameRate(getSynthesisEngine().getFrameRate()); + } + } + + public void setupGrain(Grain grain, int i) { + double temp = rate.getValues()[i] * calculateOctaveScaler(rateRange.getValues()[i]); + grain.setRate(temp); + + // Scale the amplitude range so that we never go above + // original amplitude. + double base = amplitude.getValues()[i]; + double offset = base * Math.random() * amplitudeRange.getValues()[i]; + grain.setAmplitude(base - offset); + } + + public void allocate(int numGrains) { + Grain[] grainArray = new Grain[numGrains]; + for (int i = 0; i < numGrains; i++) { + Grain grain = new Grain(new GrainSourceSine(), new RaisedCosineEnvelope()); + grainArray[i] = grain; + } + setGrainArray(grainArray); + } + + @Override + public UnitOutputPort getOutput() { + return output; + } + + private double calculateOctaveScaler(double rangeValue) { + double octaveRange = 0.5 * randomizer.nextRandomDouble() * rangeValue; + return Math.pow(2.0, octaveRange); + } + + @Override + public void generate(int start, int limit) { + double[] outputs = output.getValues(); + double[] amplitudes = amplitude.getValues(); + // double frp = getSynthesisEngine().getFramePeriod(); + for (int i = start; i < limit; i++) { + double result = 0.0; + + // Mix the grains together. + for (GrainState grainState : states) { + result += grainState.next(i); + } + + outputs[i] = result * amplitudes[i] * countScaler; + } + + } +} diff --git a/src/main/java/com/jsyn/unitgen/GrainScheduler.java b/src/main/java/com/jsyn/unitgen/GrainScheduler.java new file mode 100644 index 0000000..df9c25e --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/GrainScheduler.java @@ -0,0 +1,44 @@ +/* + * Copyright 2011 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.unitgen; + +/** + * Defines a class that can schedule the execution of Grains in a GrainFarm. This is mostly for + * internal use. + * + * @author Phil Burk (C) 2011 Mobileer Inc + */ +public interface GrainScheduler { + + /** + * Calculate time in seconds for the next gap between grains. + * + * @param duration + * @param density + * @return seconds before next grain + */ + double nextGap(double duration, double density); + + /** + * Calculate duration in seconds for the next grains. + * + * @param suggestedDuration + * @return duration of grain seconds + */ + double nextDuration(double suggestedDuration); + +} diff --git a/src/main/java/com/jsyn/unitgen/GrainSource.java b/src/main/java/com/jsyn/unitgen/GrainSource.java new file mode 100644 index 0000000..1d5c522 --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/GrainSource.java @@ -0,0 +1,36 @@ +/* + * Copyright 2011 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.unitgen; + +/** + * Defines classes that can provide the signal inside a Grain. + * + * @author Phil Burk (C) 2011 Mobileer Inc + */ +public interface GrainSource { + double getFrameRate(); + + void setFrameRate(double frameRate); + + /** Generate one more value or the source signal. */ + double next(); + + /** Reset the internal phase of the grain. */ + void reset(); + + void setRate(double rate); +} diff --git a/src/main/java/com/jsyn/unitgen/GrainSourceSine.java b/src/main/java/com/jsyn/unitgen/GrainSourceSine.java new file mode 100644 index 0000000..0af9cbd --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/GrainSourceSine.java @@ -0,0 +1,51 @@ +/* + * Copyright 2011 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.unitgen; + +/** + * A simple sine wave generator for a Grain. This uses the same fast Taylor expansion that the + * SineOscillator uses. + * + * @author Phil Burk (C) 2011 Mobileer Inc + */ +public class GrainSourceSine extends GrainCommon implements GrainSource { + protected double phase; + protected double phaseIncrement; + + public GrainSourceSine() { + setRate(1.0); + } + + public void setPhaseIncrement(double phaseIncrement) { + this.phaseIncrement = phaseIncrement; + } + + @Override + public double next() { + phase += phaseIncrement; + if (phase > 1.0) { + phase -= 2.0; + } + return SineOscillator.fastSin(phase); + } + + @Override + public void setRate(double rate) { + setPhaseIncrement(rate * 0.1 / Math.PI); + } + +} diff --git a/src/main/java/com/jsyn/unitgen/IFFT.java b/src/main/java/com/jsyn/unitgen/IFFT.java new file mode 100644 index 0000000..307acd2 --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/IFFT.java @@ -0,0 +1,36 @@ +/* + * Copyright 2013 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.unitgen; + +/** + * Periodically transform the complex input spectrum using an IFFT to a complex signal stream. + * + * @author Phil Burk (C) 2013 Mobileer Inc + * @version 016 + * @see FFT + */ +public class IFFT extends FFTBase { + + public IFFT() { + super(); + } + + @Override + protected int getSign() { + return -1; // -1 for IFFT + } +} diff --git a/src/main/java/com/jsyn/unitgen/ImpulseOscillator.java b/src/main/java/com/jsyn/unitgen/ImpulseOscillator.java new file mode 100644 index 0000000..8c676f3 --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/ImpulseOscillator.java @@ -0,0 +1,59 @@ +/* + * 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.unitgen; + +/** + * Narrow impulse oscillator. An impulse is only one sample wide. It is useful for pinging filters + * or generating an "impulse response". + * + * @author Phil Burk (C) 2009 Mobileer Inc + */ +public class ImpulseOscillator extends UnitOscillator { + + @Override + public void generate(int start, int limit) { + double[] frequencies = frequency.getValues(); + double[] amplitudes = amplitude.getValues(); + double[] outputs = output.getValues(); + + // Variables have a single value. + double currentPhase = phase.getValue(); + + double inverseNyquist = synthesisEngine.getInverseNyquist(); + + for (int i = start; i < limit; i++) { + /* Generate sawtooth phasor to provide phase for impulse generation. */ + double phaseIncrement = frequencies[i] * inverseNyquist; + currentPhase += phaseIncrement; + + double ampl = amplitudes[i]; + double result = 0.0; + if (currentPhase >= 1.0) { + currentPhase -= 2.0; + result = ampl; + } else if (currentPhase < -1.0) { + currentPhase += 2.0; + result = ampl; + } + outputs[i] = result; + } + + // Value needs to be saved for next time. + phase.setValue(currentPhase); + } + +} diff --git a/src/main/java/com/jsyn/unitgen/ImpulseOscillatorBL.java b/src/main/java/com/jsyn/unitgen/ImpulseOscillatorBL.java new file mode 100644 index 0000000..23686b8 --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/ImpulseOscillatorBL.java @@ -0,0 +1,39 @@ +/* + * 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.unitgen; + +import com.jsyn.engine.MultiTable; + +/** + * Impulse oscillator created by differentiating a sawtoothBL. A band limited impulse is very narrow + * but is slightly wider than one sample. + * + * @author Phil Burk (C) 2009 Mobileer Inc + */ +public class ImpulseOscillatorBL extends SawtoothOscillatorBL { + private double previous = 0.0; + + @Override + protected double generateBL(MultiTable multiTable, double currentPhase, + double positivePhaseIncrement, double flevel, int i) { + double saw = multiTable.calculateSawtooth(currentPhase, positivePhaseIncrement, flevel); + double result = previous - saw; + previous = saw; + return result; + } + +} diff --git a/src/main/java/com/jsyn/unitgen/Integrate.java b/src/main/java/com/jsyn/unitgen/Integrate.java new file mode 100644 index 0000000..50831d2 --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/Integrate.java @@ -0,0 +1,82 @@ +/* + * Copyright 1997 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.unitgen; + +import com.jsyn.ports.UnitInputPort; +import com.jsyn.ports.UnitOutputPort; + +/** + * IntegrateUnit unit. + *

+ * Output accumulated sum of the input signal. This can be used to transform one signal into + * another, or to generate ramps between the limits by setting the input signal positive or + * negative. For a "leaky integrator" use a FilterOnePoleOneZero. + *

+ * + *
+ * output = output + input;
+ * if (output < lowerLimit)
+ *     output = lowerLimit;
+ * else if (output > upperLimit)
+ *     output = upperLimit;
+ * 
+ * + * @author (C) 1997-2011 Phil Burk, Mobileer Inc + * @see FilterOnePoleOneZero + */ +public class Integrate extends UnitGenerator { + public UnitInputPort input; + /** + * Output will be stopped internally from going below this value. Default is -1.0. + */ + public UnitInputPort lowerLimit; + /** + * Output will be stopped internally from going above this value. Default is +1.0. + */ + public UnitInputPort upperLimit; + public UnitOutputPort output; + + private double accum; + + /* Define Unit Ports used by connect() and set(). */ + public Integrate() { + addPort(input = new UnitInputPort("Input")); + addPort(lowerLimit = new UnitInputPort("LowerLimit", -1.0)); + addPort(upperLimit = new UnitInputPort("UpperLimit", 1.0)); + addPort(output = new UnitOutputPort("Output")); + } + + @Override + public void generate(int start, int limit) { + double[] inputs = input.getValues(); + double[] lowerLimits = lowerLimit.getValues(); + double[] upperLimits = upperLimit.getValues(); + double[] outputs = output.getValues(); + + for (int i = start; i < limit; i++) { + accum += inputs[i]; // INTEGRATE + + // clip to limits + if (accum > upperLimits[i]) + accum = upperLimits[i]; + else if (accum < lowerLimits[i]) + accum = lowerLimits[i]; + + outputs[i] = accum; + } + } +} diff --git a/src/main/java/com/jsyn/unitgen/InterpolatingDelay.java b/src/main/java/com/jsyn/unitgen/InterpolatingDelay.java new file mode 100644 index 0000000..24de4f9 --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/InterpolatingDelay.java @@ -0,0 +1,117 @@ +/* + * Copyright 1997 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.unitgen; + +import com.jsyn.ports.UnitInputPort; + +/** + * InterpolatingDelay + *

+ * InterpolatingDelay provides a variable time delay with an input and output. The internal data + * format is 32-bit floating point. The amount of delay can be varied from 0.0 to a time in seconds + * corresponding to the numFrames allocated. The fractional delay values are calculated by linearly + * interpolating between adjacent values in the delay line. + *

+ * This unit can be used to implement time varying delay effects such as a flanger or a chorus. It + * can also be used to implement physical models of acoustic instruments, or other tunable delay + * based resonant systems. + *

+ * + * @author (C) 1997-2011 Phil Burk, Mobileer Inc + * @see Delay + */ + +public class InterpolatingDelay extends UnitFilter { + /** + * Delay time in seconds. This value will converted to frames and clipped between zero and the + * numFrames value passed to allocate(). The minimum and default delay time is 0.0. + */ + public UnitInputPort delay; + + private float[] buffer; + private int cursor; + private int numFrames; + + public InterpolatingDelay() { + addPort(delay = new UnitInputPort("Delay")); + } + + /** + * Allocate memory for the delay buffer. For a 2 second delay at 44100 Hz sample rate you will + * need at least 88200 samples. + * + * @param numFrames size of the float array to hold the delayed samples + */ + public void allocate(int numFrames) { + this.numFrames = numFrames; + // Allocate extra frame for guard point to speed up interpolation. + buffer = new float[numFrames + 1]; + } + + @Override + public void generate(int start, int limit) { + double[] inputs = input.getValues(); + double[] outputs = output.getValues(); + double[] delays = delay.getValues(); + + for (int i = start; i < limit; i++) { + // This should be at the beginning of the loop + // because the guard point should == buffer[0]. + if (cursor == numFrames) { + // Write guard point! Must allocate one extra sample. + buffer[numFrames] = (float) inputs[i]; + cursor = 0; + } + + buffer[cursor] = (float) inputs[i]; + + /* Convert delay time to a clipped frame offset. */ + double delayFrames = delays[i] * getFrameRate(); + + // Clip to zero delay. + if (delayFrames <= 0.0) { + outputs[i] = buffer[cursor]; + } else { + // Clip to maximum delay. + if (delayFrames >= numFrames) { + delayFrames = numFrames - 1; + } + + // Calculate fractional index into delay buffer. + double readIndex = cursor - delayFrames; + if (readIndex < 0.0) { + readIndex += numFrames; + } + // setup for interpolation. + // We know readIndex is > 0 so we do not need to call floor(). + int iReadIndex = (int) readIndex; + double frac = readIndex - iReadIndex; + + // Get adjacent values relying on guard point to prevent overflow. + double val0 = buffer[iReadIndex]; + double val1 = buffer[iReadIndex + 1]; + + // Interpolate new value. + outputs[i] = val0 + (frac * (val1 - val0)); + } + + cursor += 1; + } + + } + +} diff --git a/src/main/java/com/jsyn/unitgen/Latch.java b/src/main/java/com/jsyn/unitgen/Latch.java new file mode 100644 index 0000000..0518f69 --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/Latch.java @@ -0,0 +1,53 @@ +/* + * Copyright 1997 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.unitgen; + +import com.jsyn.ports.UnitInputPort; + +/** + * Latch or hold an input value. + *

+ * Pass a value unchanged if gate true, otherwise output held value. + *

+ * output = ( gate > 0.0 ) ? input : previous_output; + * + * @author (C) 1997-2010 Phil Burk, Mobileer Inc + * @see EdgeDetector + */ +public class Latch extends UnitFilter { + public UnitInputPort gate; + private double held; + + /* Define Unit Ports used by connect() and set(). */ + public Latch() { + addPort(gate = new UnitInputPort("Gate")); + } + + @Override + public void generate(int start, int limit) { + double[] inputs = input.getValues(); + double[] gates = gate.getValues(); + double[] outputs = output.getValues(); + + for (int i = start; i < limit; i++) { + if (gates[i] > 0.0) { + held = inputs[i]; + } + outputs[i] = held; + } + } +} diff --git a/src/main/java/com/jsyn/unitgen/LatchZeroCrossing.java b/src/main/java/com/jsyn/unitgen/LatchZeroCrossing.java new file mode 100644 index 0000000..9e6c011 --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/LatchZeroCrossing.java @@ -0,0 +1,72 @@ +/* + * Copyright 2010 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.unitgen; + +import com.jsyn.ports.UnitInputPort; +import com.jsyn.ports.UnitOutputPort; + +/** + * Latches when input crosses zero. + *

+ * Pass a value unchanged if gate true, otherwise pass input unchanged until input crosses zero then + * output zero. This can be used to turn off a sound at a zero crossing so there is no pop. + *

+ * + * @author (C) 2010 Phil Burk, Mobileer Inc + * @see Latch + * @see Minimum + */ +public class LatchZeroCrossing extends UnitGenerator { + public UnitInputPort input; + public UnitInputPort gate; + public UnitOutputPort output; + private double held; + private boolean crossed; + + /* Define Unit Ports used by connect() and set(). */ + public LatchZeroCrossing() { + addPort(input = new UnitInputPort("Input")); + addPort(gate = new UnitInputPort("Gate", 1.0)); + addPort(output = new UnitOutputPort("Output")); + } + + @Override + public void generate(int start, int limit) { + double[] inputs = input.getValues(); + double[] gates = gate.getValues(); + double[] outputs = output.getValues(); + + for (int i = start; i < limit; i++) { + double current = inputs[i]; + if (gates[i] > 0.0) { + held = current; + crossed = false; + } else { + // If we haven't already seen a zero crossing then look for one. + if (!crossed) { + if ((held * current) <= 0.0) { + held = 0.0; + crossed = true; + } else { + held = current; + } + } + } + outputs[i] = held; + } + } +} diff --git a/src/main/java/com/jsyn/unitgen/LineIn.java b/src/main/java/com/jsyn/unitgen/LineIn.java new file mode 100644 index 0000000..aeef965 --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/LineIn.java @@ -0,0 +1,51 @@ +/* + * 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.unitgen; + +import com.jsyn.Synthesizer; +import com.jsyn.ports.UnitOutputPort; + +/** + * External audio input is sent to the output of this unit. The LineIn provides a stereo signal + * containing channels 0 and 1. For LineIn to work you must call the Synthesizer start() method with + * numInputChannels > 0. + * + * @author Phil Burk (C) 2009 Mobileer Inc + * @see Synthesizer + * @see ChannelIn + * @see LineOut + */ +public class LineIn extends UnitGenerator { + public UnitOutputPort output; + + public LineIn() { + addPort(output = new UnitOutputPort(2, "Output")); + } + + @Override + public void generate(int start, int limit) { + double[] outputs0 = output.getValues(0); + double[] outputs1 = output.getValues(1); + double[] buffer0 = synthesisEngine.getInputBuffer(0); + double[] buffer1 = synthesisEngine.getInputBuffer(1); + for (int i = start; i < limit; i++) { + outputs0[i] = buffer0[i]; + outputs1[i] = buffer1[i]; + } + } + +} diff --git a/src/main/java/com/jsyn/unitgen/LineOut.java b/src/main/java/com/jsyn/unitgen/LineOut.java new file mode 100644 index 0000000..29c8ce7 --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/LineOut.java @@ -0,0 +1,57 @@ +/* + * 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.unitgen; + +import com.jsyn.ports.UnitInputPort; + +/** + * Input audio is sent to the external audio output device. + * + * @author Phil Burk (C) 2009 Mobileer Inc + */ +public class LineOut extends UnitGenerator implements UnitSink { + public UnitInputPort input; + + public LineOut() { + addPort(input = new UnitInputPort(2, "Input")); + } + + @Override + public void generate(int start, int limit) { + double[] inputs0 = input.getValues(0); + double[] inputs1 = input.getValues(1); + double[] buffer0 = synthesisEngine.getOutputBuffer(0); + double[] buffer1 = synthesisEngine.getOutputBuffer(1); + for (int i = start; i < limit; i++) { + buffer0[i] += inputs0[i]; + buffer1[i] += inputs1[i]; + } + } + + /** + * This unit won't do anything unless you start() it. + */ + @Override + public boolean isStartRequired() { + return true; + } + + @Override + public UnitInputPort getInput() { + return input; + } +} diff --git a/src/main/java/com/jsyn/unitgen/LinearRamp.java b/src/main/java/com/jsyn/unitgen/LinearRamp.java new file mode 100644 index 0000000..cad53d5 --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/LinearRamp.java @@ -0,0 +1,94 @@ +/* + * Copyright 1997 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.unitgen; + +import com.jsyn.ports.UnitInputPort; +import com.jsyn.ports.UnitVariablePort; + +/** + * Output approaches Input linearly. + *

+ * When you change the value of the input port, the ramp will start changing from its current output + * value toward the value of input. An internal phase value will go from 0.0 to 1.0 at a rate + * controlled by time. When the internal phase reaches 1.0, the output will equal input. + *

+ * + * @author (C) 1997 Phil Burk, SoftSynth.com + * @see ExponentialRamp + * @see AsymptoticRamp + * @see ContinuousRamp + */ +public class LinearRamp extends UnitFilter { + /** Time in seconds to get to the input value. */ + public UnitInputPort time; + public UnitVariablePort current; + + private double source; + private double phase; + private double target; + private double timeHeld = 0.0; + private double rate = 1.0; + + public LinearRamp() { + addPort(time = new UnitInputPort("Time")); + addPort(current = new UnitVariablePort("Current")); + } + + @Override + public void generate(int start, int limit) { + double[] outputs = output.getValues(); + double currentInput = input.getValues()[0]; + double currentValue = current.getValue(); + + // If input has changed, start new segment. + // Equality check is OK because we set them exactly equal below. + if (currentInput != target) + { + source = currentValue; + phase = 0.0; + target = currentInput; + } + + if (currentValue == target) { + // at end of ramp + for (int i = start; i < limit; i++) { + outputs[i] = currentValue; + } + } else { + // in middle of ramp + double currentTime = time.getValues()[0]; + // Has time changed? + if (currentTime != timeHeld) { + rate = convertTimeToRate(currentTime); + timeHeld = currentTime; + } + + for (int i = start; i < limit; i++) { + if (phase < 1.0) { + /* Interpolate current. */ + currentValue = source + (phase * (target - source)); + phase += rate; + } else { + currentValue = target; + } + outputs[i] = currentValue; + } + } + + current.setValue(currentValue); + } +} diff --git a/src/main/java/com/jsyn/unitgen/Maximum.java b/src/main/java/com/jsyn/unitgen/Maximum.java new file mode 100644 index 0000000..296e5da --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/Maximum.java @@ -0,0 +1,42 @@ +/* + * 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.unitgen; + +/** + * + Output largest of inputA or inputB. + * + *

+ * output = (inputA > InputB) ? inputA : InputB;
+ * 
+ * + * @author Phil Burk (C) 2009 Mobileer Inc + * @version 016 + * @see Minimum + */ +public class Maximum extends UnitBinaryOperator { + @Override + public void generate(int start, int limit) { + double[] aValues = inputA.getValues(); + double[] bValues = inputB.getValues(); + double[] outputs = output.getValues(); + + for (int i = start; i < limit; i++) { + outputs[i] = (aValues[i] > bValues[i]) ? aValues[i] : bValues[i]; + } + } +} diff --git a/src/main/java/com/jsyn/unitgen/Minimum.java b/src/main/java/com/jsyn/unitgen/Minimum.java new file mode 100644 index 0000000..046387e --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/Minimum.java @@ -0,0 +1,43 @@ +/* + * 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.unitgen; + +/** + * + Output smallest of inputA or inputB. + * + *
+ * output = (inputA < InputB) ? inputA : InputB;
+ * 
+ * + * @author Phil Burk (C) 2009 Mobileer Inc + * @version 016 + * @see Maximum + */ +public class Minimum extends UnitBinaryOperator { + + @Override + public void generate(int start, int limit) { + double[] aValues = inputA.getValues(); + double[] bValues = inputB.getValues(); + double[] outputs = output.getValues(); + + for (int i = start; i < limit; i++) { + outputs[i] = (aValues[i] < bValues[i]) ? aValues[i] : bValues[i]; + } + } +} diff --git a/src/main/java/com/jsyn/unitgen/MixerMono.java b/src/main/java/com/jsyn/unitgen/MixerMono.java new file mode 100644 index 0000000..f4c7d7d --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/MixerMono.java @@ -0,0 +1,77 @@ +/* + * Copyright 2014 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.unitgen; + +import com.jsyn.ports.UnitInputPort; +import com.jsyn.ports.UnitOutputPort; + +/** + * Multi-channel mixer with mono output and master amplitude. + * + * @author Phil Burk (C) 2014 Mobileer Inc + * @see MixerMonoRamped + * @see MixerStereo + */ +public class MixerMono extends UnitGenerator implements UnitSink, UnitSource { + public UnitInputPort input; + /** + * Linear gain for the corresponding input. + */ + public UnitInputPort gain; + /** + * Master gain control. + */ + public UnitInputPort amplitude; + public UnitOutputPort output; + + public MixerMono(int numInputs) { + addPort(input = new UnitInputPort(numInputs, "Input")); + addPort(gain = new UnitInputPort(numInputs, "Gain", 1.0)); + addPort(amplitude = new UnitInputPort("Amplitude", 1.0)); + addPort(output = new UnitOutputPort(getNumOutputs(), "Output")); + } + + public int getNumOutputs() { + return 1; + } + + @Override + public void generate(int start, int limit) { + double[] amplitudes = amplitude.getValues(0); + double[] outputs = output.getValues(0); + for (int i = start; i < limit; i++) { + double sum = 0; + for (int n = 0; n < input.getNumParts(); n++) { + double[] inputs = input.getValues(n); + double[] gains = gain.getValues(n); + sum += inputs[i] * gains[i]; + } + outputs[i] = sum * amplitudes[i]; + } + } + + @Override + public UnitInputPort getInput() { + return input; + } + + @Override + public UnitOutputPort getOutput() { + return output; + } + +} diff --git a/src/main/java/com/jsyn/unitgen/MixerMonoRamped.java b/src/main/java/com/jsyn/unitgen/MixerMonoRamped.java new file mode 100644 index 0000000..30f5342 --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/MixerMonoRamped.java @@ -0,0 +1,54 @@ +/* + * Copyright 2014 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.unitgen; + +/** + * Similar to MixerMono but the gain and amplitude ports are smoothed using short linear ramps. So + * you can control them with knobs and not hear any zipper noise. + * + * @author Phil Burk (C) 2014 Mobileer Inc + */ +public class MixerMonoRamped extends MixerMono { + private Unzipper[] unzippers; + private Unzipper amplitudeUnzipper; + + public MixerMonoRamped(int numInputs) { + super(numInputs); + unzippers = new Unzipper[numInputs]; + for (int i = 0; i < numInputs; i++) { + unzippers[i] = new Unzipper(); + } + amplitudeUnzipper = new Unzipper(); + } + + @Override + public void generate(int start, int limit) { + double[] amplitudes = amplitude.getValues(0); + double[] outputs = output.getValues(0); + for (int i = start; i < limit; i++) { + double sum = 0; + for (int n = 0; n < input.getNumParts(); n++) { + double[] inputs = input.getValues(n); + double[] gains = gain.getValues(n); + double smoothGain = unzippers[n].smooth(gains[i]); + sum += inputs[i] * smoothGain; + } + outputs[i] = sum * amplitudeUnzipper.smooth(amplitudes[i]); + } + } + +} diff --git a/src/main/java/com/jsyn/unitgen/MixerStereo.java b/src/main/java/com/jsyn/unitgen/MixerStereo.java new file mode 100644 index 0000000..218546e --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/MixerStereo.java @@ -0,0 +1,94 @@ +/* + * Copyright 2014 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.unitgen; + +import com.jsyn.ports.UnitInputPort; + +/** + * Mixer with monophonic inputs and two channels of output. Each signal can be panned left or right + * using an equal power curve. The "left" signal will be on output part zero. The "right" signal + * will be on output part one. + * + * @author Phil Burk (C) 2014 Mobileer Inc + * @see MixerMono + * @see MixerStereoRamped + */ +public class MixerStereo extends MixerMono { + /** + * Set to -1.0 for all left channel, 0.0 for center, or +1.0 for all right. Or anywhere in + * between. + */ + public UnitInputPort pan; + protected PanTracker[] panTrackers; + + static class PanTracker { + double previousPan = Double.MAX_VALUE; // so we update immediately + double leftGain; + double rightGain; + + public void update(double pan) { + if (pan != previousPan) { + // fastSine range is -1.0 to +1.0 for full cycle. + // We want a quarter cycle. So map -1.0 to +1.0 into 0.0 to 0.5 + double phase = pan * 0.25 + 0.25; + leftGain = SineOscillator.fastSin(0.5 - phase); + rightGain = SineOscillator.fastSin(phase); + previousPan = pan; + } + } + } + + public MixerStereo(int numInputs) { + super(numInputs); + addPort(pan = new UnitInputPort(numInputs, "Pan")); + pan.setup(-1.0, 0.0, 1.0); + panTrackers = new PanTracker[numInputs]; + for (int i = 0; i < numInputs; i++) { + panTrackers[i] = new PanTracker(); + } + } + + @Override + public int getNumOutputs() { + return 2; + } + + @Override + public void generate(int start, int limit) { + double[] amplitudes = amplitude.getValues(0); + double[] outputs0 = output.getValues(0); + double[] outputs1 = output.getValues(1); + for (int i = start; i < limit; i++) { + double sum0 = 0.0; + double sum1 = 0.0; + for (int n = 0; n < input.getNumParts(); n++) { + double[] inputs = input.getValues(n); + double[] gains = gain.getValues(n); + double[] pans = pan.getValues(n); + PanTracker panTracker = panTrackers[n]; + panTracker.update(pans[i]); + double scaledInput = inputs[i] * gains[i]; + sum0 += scaledInput * panTracker.leftGain; + sum1 += scaledInput * panTracker.rightGain; + } + double amp = amplitudes[i]; + outputs0[i] = sum0 * amp; + outputs1[i] = sum1 * amp; + } + } + +} diff --git a/src/main/java/com/jsyn/unitgen/MixerStereoRamped.java b/src/main/java/com/jsyn/unitgen/MixerStereoRamped.java new file mode 100644 index 0000000..6f3bfcc --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/MixerStereoRamped.java @@ -0,0 +1,71 @@ +/* + * Copyright 2014 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.unitgen; + +/** + * Similar to MixerStereo but the gain, pan and amplitude ports are smoothed using short linear + * ramps. So you can control them with knobs and not hear any zipper noise. + * + * @author Phil Burk (C) 2014 Mobileer Inc + */ +public class MixerStereoRamped extends MixerStereo { + private Unzipper[] gainUnzippers; + private Unzipper[] panUnzippers; + private Unzipper amplitudeUnzipper; + + public MixerStereoRamped(int numInputs) { + super(numInputs); + gainUnzippers = new Unzipper[numInputs]; + for (int i = 0; i < numInputs; i++) { + gainUnzippers[i] = new Unzipper(); + } + panUnzippers = new Unzipper[numInputs]; + for (int i = 0; i < numInputs; i++) { + panUnzippers[i] = new Unzipper(); + } + amplitudeUnzipper = new Unzipper(); + } + + @Override + public void generate(int start, int limit) { + double[] amplitudes = amplitude.getValues(0); + double[] outputs0 = output.getValues(0); + double[] outputs1 = output.getValues(1); + for (int i = start; i < limit; i++) { + double sum0 = 0; + double sum1 = 0; + for (int n = 0; n < input.getNumParts(); n++) { + double[] inputs = input.getValues(n); + double[] gains = gain.getValues(n); + double[] pans = pan.getValues(n); + + PanTracker panTracker = panTrackers[n]; + double smoothPan = panUnzippers[n].smooth(pans[i]); + panTracker.update(smoothPan); + + double smoothGain = gainUnzippers[n].smooth(gains[i]); + double scaledInput = inputs[i] * smoothGain; + sum0 += scaledInput * panTracker.leftGain; + sum1 += scaledInput * panTracker.rightGain; + } + double amp = amplitudeUnzipper.smooth(amplitudes[i]); + outputs0[i] = sum0 * amp; + outputs1[i] = sum1 * amp; + } + } + +} diff --git a/src/main/java/com/jsyn/unitgen/MonoStreamWriter.java b/src/main/java/com/jsyn/unitgen/MonoStreamWriter.java new file mode 100644 index 0000000..0fb6f40 --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/MonoStreamWriter.java @@ -0,0 +1,49 @@ +/* + * 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.unitgen; + +import java.io.IOException; + +import com.jsyn.io.AudioOutputStream; +import com.jsyn.ports.UnitInputPort; + +/** + * Write one sample per audio frame to an AudioOutputStream with no interpolation. + * + * Note that you must call start() on this unit because it does not have an output for pulling data. + * + * @author Phil Burk (C) 2009 Mobileer Inc + */ +public class MonoStreamWriter extends UnitStreamWriter { + public MonoStreamWriter() { + addPort(input = new UnitInputPort("Input")); + } + + @Override + public void generate(int start, int limit) { + double[] inputs = input.getValues(); + AudioOutputStream output = outputStream; + if (output != null) { + int count = limit - start; + try { + output.write(inputs, start, count); + } catch (IOException ignored) { + } + } + } + +} diff --git a/src/main/java/com/jsyn/unitgen/MorphingOscillatorBL.java b/src/main/java/com/jsyn/unitgen/MorphingOscillatorBL.java new file mode 100644 index 0000000..7ca440d --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/MorphingOscillatorBL.java @@ -0,0 +1,72 @@ +/* + * 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.unitgen; + +import com.jsyn.engine.MultiTable; +import com.jsyn.ports.UnitInputPort; + +/** + * Oscillator that can change its shape from sine to sawtooth to pulse. + * + * @author Phil Burk (C) 2016 Mobileer Inc + */ +public class MorphingOscillatorBL extends PulseOscillatorBL { + /** + * Controls the shape of the waveform. + * The shape varies continuously from a sine wave at -1.0, + * to a sawtooth at 0.0 to a pulse wave at 1.0. + */ + public UnitInputPort shape; + + public MorphingOscillatorBL() { + addPort(shape = new UnitInputPort("Shape")); + shape.setMinimum(-1.0); + shape.setMaximum(1.0); + } + + @Override + protected double generateBL(MultiTable multiTable, double currentPhase, + double positivePhaseIncrement, double flevel, int i) { + double[] shapes = shape.getValues(); + double shape = shapes[i]; + + if (shape < 0.0) { + // Squeeze flevel towards the pure sine table. + flevel += flevel * shape; + return multiTable.calculateSawtooth(currentPhase, positivePhaseIncrement, flevel); + } else { + double[] widths = width.getValues(); + double width = widths[i]; + width = (width > 0.999) ? 0.999 : ((width < -0.999) ? -0.999 : width); + + double val1 = multiTable.calculateSawtooth(currentPhase, positivePhaseIncrement, flevel); + // Generate second sawtooth so we can add them together. + double phase2 = currentPhase + 1.0 - width; // 180 degrees out of phase + if (phase2 >= 1.0) { + phase2 -= 2.0; + } + double val2 = multiTable.calculateSawtooth(phase2, positivePhaseIncrement, flevel); + + /* + * Need to adjust amplitude based on positive phaseInc. little less than half at + * Nyquist/2.0! + */ + double scale = 1.0 - positivePhaseIncrement; + return scale * (val1 - ((val2 + width) * shape)); // apply shape morphing + } + } +} diff --git a/src/main/java/com/jsyn/unitgen/MultiPassThrough.java b/src/main/java/com/jsyn/unitgen/MultiPassThrough.java new file mode 100644 index 0000000..9125fc3 --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/MultiPassThrough.java @@ -0,0 +1,70 @@ +/* + * Copyright 2011 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.unitgen; + +import com.jsyn.ports.UnitInputPort; +import com.jsyn.ports.UnitOutputPort; + +/** + * Pass the input through to the output unchanged. This is often used for distributing a signal to + * multiple ports inside a circuit. It can also be used as a summing node, in other words, a mixer. + * + * This is just like PassThrough except the input and output ports have multiple parts. + * The default is two parts, ie. stereo. + * + * @author Phil Burk (C) 2016 Mobileer Inc + * @see Circuit + * @see PassThrough + */ +public class MultiPassThrough extends UnitGenerator implements UnitSink, UnitSource { + public UnitInputPort input; + public UnitOutputPort output; + private final int mNumParts; + + /* Define Unit Ports used by connect() and set(). */ + public MultiPassThrough(int numParts) { + mNumParts = numParts; + addPort(input = new UnitInputPort(numParts, "Input")); + addPort(output = new UnitOutputPort(numParts, "Output")); + } + + public MultiPassThrough() { + this(2); // stereo + } + + @Override + public UnitInputPort getInput() { + return input; + } + + @Override + public UnitOutputPort getOutput() { + return output; + } + + @Override + public void generate(int start, int limit) { + for (int partIndex = 0; partIndex < mNumParts; partIndex++) { + double[] inputs = input.getValues(partIndex); + double[] outputs = output.getValues(partIndex); + + for (int i = start; i < limit; i++) { + outputs[i] = inputs[i]; + } + } + } +} diff --git a/src/main/java/com/jsyn/unitgen/Multiply.java b/src/main/java/com/jsyn/unitgen/Multiply.java new file mode 100644 index 0000000..ded7646 --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/Multiply.java @@ -0,0 +1,64 @@ +/* + * 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.unitgen; + +import com.jsyn.ports.UnitInputPort; +import com.jsyn.ports.UnitOutputPort; + +/** + * This unit multiplies its two inputs.
+ * + *
+ * output = inputA * inputB
+ * 
+ * + *
+ * Note that some units have an amplitude port, which controls an internal multiply. So you may not + * need this unit. + * + * @author Phil Burk (C) 2009 Mobileer Inc + * @version 016 + * @see MultiplyAdd + * @see Subtract + */ +public class Multiply extends UnitBinaryOperator { + public Multiply() { + } + + /** Connect a to inputA and b to inputB. */ + public Multiply(UnitOutputPort a, UnitOutputPort b) { + a.connect(inputA); + b.connect(inputB); + } + + /** Connect a to inputA and b to inputB and connect output to c. */ + public Multiply(UnitOutputPort a, UnitOutputPort b, UnitInputPort c) { + this(a, b); + output.connect(c); + } + + @Override + public void generate(int start, int limit) { + double[] aValues = inputA.getValues(); + double[] bValues = inputB.getValues(); + double[] outputs = output.getValues(); + for (int i = start; i < limit; i++) { + outputs[i] = aValues[i] * bValues[i]; + } + } + +} diff --git a/src/main/java/com/jsyn/unitgen/MultiplyAdd.java b/src/main/java/com/jsyn/unitgen/MultiplyAdd.java new file mode 100644 index 0000000..adbee6c --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/MultiplyAdd.java @@ -0,0 +1,57 @@ +/* + * 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.unitgen; + +import com.jsyn.ports.UnitInputPort; +import com.jsyn.ports.UnitOutputPort; + +/** + *
+ * output = (inputA * inputB) + inputC
+ * 
+ * + * @author Phil Burk (C) 2009 Mobileer Inc + * @version 016 + * @see Multiply + * @see Add + */ +public class MultiplyAdd extends UnitGenerator { + public UnitInputPort inputA; + public UnitInputPort inputB; + public UnitInputPort inputC; + public UnitOutputPort output; + + /* Define Unit Ports used by connect() and set(). */ + public MultiplyAdd() { + addPort(inputA = new UnitInputPort("InputA")); + addPort(inputB = new UnitInputPort("InputB")); + addPort(inputC = new UnitInputPort("InputC")); + addPort(output = new UnitOutputPort("Output")); + } + + @Override + public void generate(int start, int limit) { + double[] aValues = inputA.getValues(); + double[] bValues = inputB.getValues(); + double[] cValues = inputC.getValues(); + double[] outputs = output.getValues(); + for (int i = start; i < limit; i++) { + outputs[i] = (aValues[i] * bValues[i]) + cValues[i]; + } + } + +} diff --git a/src/main/java/com/jsyn/unitgen/Pan.java b/src/main/java/com/jsyn/unitgen/Pan.java new file mode 100644 index 0000000..bc90984 --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/Pan.java @@ -0,0 +1,64 @@ +/* + * Copyright 1997 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.unitgen; + +import com.jsyn.ports.UnitInputPort; +import com.jsyn.ports.UnitOutputPort; + +/** + * Pan unit. The profile is constant amplitude and not constant energy. + *

+ * Takes an input and pans it between two outputs based on value of pan. When pan is -1, output[0] + * is input, and output[1] is zero. When pan is 0, output[0] and output[1] are both input/2. When + * pan is +1, output[0] is zero, and output[1] is input. + *

+ * + * @author (C) 1997 Phil Burk, SoftSynth.com + * @see Select + */ +public class Pan extends UnitGenerator { + public UnitInputPort input; + /** + * Pan control varies from -1.0 for full left to +1.0 for full right. Set to 0.0 for center. + */ + public UnitInputPort pan; + public UnitOutputPort output; + + public Pan() { + addPort(input = new UnitInputPort("Input")); + addPort(pan = new UnitInputPort("Pan")); + addPort(output = new UnitOutputPort(2, "Output")); + } + + @Override + public void generate(int start, int limit) { + double[] inputs = input.getValues(); + double[] panPtr = pan.getValues(); + double[] outputs_0 = output.getValues(0); + double[] outputs_1 = output.getValues(1); + + for (int i = start; i < limit; i++) { + double gainB = (panPtr[i] * 0.5) + 0.5; /* + * Scale and offset to 0.0 to 1.0 + */ + double gainA = 1.0 - gainB; + double inVal = inputs[i]; + outputs_0[i] = inVal * gainA; + outputs_1[i] = inVal * gainB; + } + } +} diff --git a/src/main/java/com/jsyn/unitgen/PanControl.java b/src/main/java/com/jsyn/unitgen/PanControl.java new file mode 100644 index 0000000..63bddd8 --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/PanControl.java @@ -0,0 +1,61 @@ +/* + * Copyright 1997 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.unitgen; + +import com.jsyn.ports.UnitInputPort; +import com.jsyn.ports.UnitOutputPort; + +/** + * PanControl unit. + *

+ * Generates control signals that can be used to control a mixer or the amplitude ports of two + * units. + * + *

+ * temp = (pan * 0.5) + 0.5;
+ * output[0] = temp;
+ * output[1] = 1.0 - temp;
+ * 
+ *

+ * + * @author (C) 1997-2009 Phil Burk, SoftSynth.com + */ +public class PanControl extends UnitGenerator { + public UnitInputPort pan; + public UnitOutputPort output; + + /* Define Unit Ports used by connect() and set(). */ + public PanControl() { + addPort(pan = new UnitInputPort("Pan")); + addPort(output = new UnitOutputPort(2, "Output", 0.0)); + } + + @Override + public void generate(int start, int limit) { + double[] panPtr = pan.getValues(); + double[] output0s = output.getValues(0); + double[] output1s = output.getValues(1); + + for (int i = start; i < limit; i++) { + double gainB = (panPtr[i] * 0.5) + 0.5; /* + * Scale and offset to 0.0 to 1.0 + */ + output0s[i] = 1.0 - gainB; + output1s[i] = gainB; + } + } +} diff --git a/src/main/java/com/jsyn/unitgen/ParabolicEnvelope.java b/src/main/java/com/jsyn/unitgen/ParabolicEnvelope.java new file mode 100644 index 0000000..6de97d9 --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/ParabolicEnvelope.java @@ -0,0 +1,110 @@ +/* + * Copyright 1997 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.unitgen; + +import com.jsyn.ports.UnitInputPort; +import com.jsyn.ports.UnitOutputPort; + +/** + * ParabolicEnvelope unit. Output goes from zero to amplitude then back to zero in a parabolic arc. + *

+ * Generate a short parabolic envelope that could be used for granular synthesis. The output starts + * at zero, peaks at the value of amplitude then returns to zero. This unit has two states, IDLE and + * RUNNING. If a trigger is received when IDLE, the envelope is started and another trigger is sent + * out the triggerOutput port. This triggerOutput can be used to latch values for the synthesis of a + * grain. If a trigger is received when RUNNING, then it is ignored and passed out the triggerPass + * port. The triggerPass can be connected to the triggerInput of another ParabolicEnvelope. Thus you + * can implement a simple grain allocation scheme by daisy chaining the triggers of + * ParabolicEnvelopes. + *

+ * The envelope is generated by a double integrator method so it uses relatively little CPU time. + * + * @author (C) 1997 Phil Burk, SoftSynth.com + * @see EnvelopeDAHDSR + */ +public class ParabolicEnvelope extends UnitGenerator { + + /** Fastest repeat rate of envelope if it were continually retriggered in Hertz. */ + public UnitInputPort frequency; + /** True value triggers envelope when in resting state. */ + public UnitInputPort triggerInput; + public UnitInputPort amplitude; + + /** Trigger output when envelope started. */ + public UnitOutputPort triggerOutput; + /** Input trigger passed out if ignored for daisy chaining. */ + public UnitOutputPort triggerPass; + public UnitOutputPort output; + + private double slope; + private double curve; + private double level; + private boolean running; + + /* Define Unit Ports used by connect() and set(). */ + public ParabolicEnvelope() { + addPort(triggerInput = new UnitInputPort("Input")); + addPort(frequency = new UnitInputPort("Frequency", UnitOscillator.DEFAULT_FREQUENCY)); + addPort(amplitude = new UnitInputPort("Amplitude", UnitOscillator.DEFAULT_AMPLITUDE)); + + addPort(output = new UnitOutputPort("Output")); + addPort(triggerOutput = new UnitOutputPort("TriggerOutput")); + addPort(triggerPass = new UnitOutputPort("TriggerPass")); + } + + @Override + public void generate(int start, int limit) { + double[] frequencies = frequency.getValues(); + double[] amplitudes = amplitude.getValues(); + double[] triggerInputs = triggerInput.getValues(); + double[] outputs = output.getValues(); + double[] triggerPasses = triggerPass.getValues(); + double[] triggerOutputs = triggerOutput.getValues(); + + for (int i = start; i < limit; i++) { + if (!running) { + if (triggerInputs[i] > 0) { + double freq = frequencies[i] * synthesisEngine.getInverseNyquist(); + freq = (freq > 1.0) ? 1.0 : ((freq < -1.0) ? -1.0 : freq); + double ampl = amplitudes[i]; + double freq2 = freq * freq; /* Square frequency. */ + slope = 4.0 * ampl * (freq - freq2); + curve = -8.0 * ampl * freq2; + level = 0.0; + triggerOutputs[i] = UnitGenerator.TRUE; + running = true; + } else { + triggerOutputs[i] = UnitGenerator.FALSE; + } + triggerPasses[i] = UnitGenerator.FALSE; + } else /* RUNNING */ + { + level += slope; + slope += curve; + if (level <= 0.0) { + level = 0.0; + running = false; + /* Autostop? - FIXME */ + } + + triggerOutputs[i] = UnitGenerator.FALSE; + triggerPasses[i] = triggerInputs[i]; + } + outputs[i] = level; + } + } +} diff --git a/src/main/java/com/jsyn/unitgen/PassThrough.java b/src/main/java/com/jsyn/unitgen/PassThrough.java new file mode 100644 index 0000000..8ac0b93 --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/PassThrough.java @@ -0,0 +1,38 @@ +/* + * Copyright 2011 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.unitgen; + +/** + * Pass the input through to the output unchanged. This is often used for distributing a signal to + * multiple ports inside a circuit. It can also be used as a summing node, in other words, a mixer. + * + * @author Phil Burk (C) 2011 Mobileer Inc + * @see Circuit + * @see MultiPassThrough + */ +public class PassThrough extends UnitFilter { + @Override + public void generate(int start, int limit) { + double[] inputs = input.getValues(); + double[] outputs = output.getValues(); + + for (int i = start; i < limit; i++) { + outputs[i] = inputs[i]; + } + + } +} diff --git a/src/main/java/com/jsyn/unitgen/PeakFollower.java b/src/main/java/com/jsyn/unitgen/PeakFollower.java new file mode 100644 index 0000000..7bf0508 --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/PeakFollower.java @@ -0,0 +1,87 @@ +/* + * Copyright 1997 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.unitgen; + +import com.jsyn.ports.UnitInputPort; +import com.jsyn.ports.UnitOutputPort; +import com.jsyn.ports.UnitVariablePort; + +/** + * Tracks the peaks of an input signal. This can be used to monitor the overall amplitude of a + * signal. The output can be used to drive color organs, vocoders, VUmeters, etc. Output drops + * exponentially when the input drops below the current output level. The output approaches zero + * based on the value on the halfLife port. + * + * @author (C) 1997-2009 Phil Burk, SoftSynth.com + */ +public class PeakFollower extends UnitGenerator { + public UnitInputPort input; + public UnitVariablePort current; + public UnitInputPort halfLife; + public UnitOutputPort output; + + private double previousHalfLife = -1.0; + private double decayScalar = 0.99; + + /* Define Unit Ports used by connect() and set(). */ + public PeakFollower() { + addPort(input = new UnitInputPort("Input")); + addPort(halfLife = new UnitInputPort(1, "HalfLife", 0.1)); + addPort(current = new UnitVariablePort("Current")); + addPort(output = new UnitOutputPort("Output")); + } + + @Override + public void generate(int start, int limit) { + double[] inputs = input.getValues(); + double[] outputs = output.getValues(); + double currentHalfLife = halfLife.getValues()[0]; + double currentValue = current.getValue(); + + if (currentHalfLife != previousHalfLife) { + decayScalar = this.convertHalfLifeToMultiplier(currentHalfLife); + previousHalfLife = currentHalfLife; + } + + double scalar = 1.0 - decayScalar; + + for (int i = start; i < limit; i++) { + double inputValue = inputs[i]; + if (inputValue < 0.0) { + inputValue = -inputValue; // absolute value + } + + if (inputValue >= currentValue) { + currentValue = inputValue; + } else { + currentValue = currentValue * scalar; + } + + outputs[i] = currentValue; + } + + /* + * When current gets close to zero, set current to zero to prevent FP underflow, which can + * cause a severe performance degradation in 'C'. + */ + if (currentValue < VERY_SMALL_FLOAT) { + currentValue = 0.0; + } + + current.setValue(currentValue); + } +} diff --git a/src/main/java/com/jsyn/unitgen/PhaseShifter.java b/src/main/java/com/jsyn/unitgen/PhaseShifter.java new file mode 100644 index 0000000..4b17245 --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/PhaseShifter.java @@ -0,0 +1,90 @@ +/* + * Copyright 2014 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.unitgen; + +import com.jsyn.ports.UnitInputPort; + +/** + * PhaseShifter effects processor. This unit emulates a common guitar pedal effect but without the + * LFO modulation. You can use your own modulation source connected to the "offset" port. Different + * frequencies are phase shifted varying amounts using a series of AllPass filters. By feeding the + * output back to the input we can get varying phase cancellation. This implementation was based on + * code posted to the music-dsp archive by Ross Bencina. http://www.musicdsp.org/files/phaser.cpp + * + * @author (C) 2014 Phil Burk, Mobileer Inc + * @see FilterLowPass + * @see FilterAllPass + * @see RangeConverter + */ + +public class PhaseShifter extends UnitFilter { + /** + * Connect an oscillator to this port to sweep the phase. A range of 0.05 to 0.4 is a good + * start. + */ + public UnitInputPort offset; + public UnitInputPort feedback; + public UnitInputPort depth; + + private double zm1; + private double[] xs; + private double[] ys; + + public PhaseShifter() { + this(6); + } + + public PhaseShifter(int numStages) { + addPort(offset = new UnitInputPort("Offset", 0.1)); + addPort(feedback = new UnitInputPort("Feedback", 0.7)); + addPort(depth = new UnitInputPort("Depth", 1.0)); + + xs = new double[numStages]; + ys = new double[numStages]; + } + + @Override + public void generate(int start, int limit) { + double[] inputs = input.getValues(); + double[] outputs = output.getValues(); + double[] feedbacks = feedback.getValues(); + double[] depths = depth.getValues(); + double[] offsets = offset.getValues(); + double gain; + + for (int i = start; i < limit; i++) { + // Support audio rate modulation. + double currentOffset = offsets[i]; + + // Prevent gain from exceeding 1.0. + gain = 1.0 - (currentOffset * currentOffset); + if (gain < -1.0) { + gain = -1.0; + } + + double x = inputs[i] + (zm1 * feedbacks[i]); + // Cascaded all-pass filters. + for (int stage = 0; stage < xs.length; stage++) { + double temp = ys[stage] = (gain * (ys[stage] - x)) + xs[stage]; + xs[stage] = x; + x = temp; + } + zm1 = x; + outputs[i] = inputs[i] + (x * depths[i]); + } + } +} diff --git a/src/main/java/com/jsyn/unitgen/PinkNoise.java b/src/main/java/com/jsyn/unitgen/PinkNoise.java new file mode 100644 index 0000000..84aa2f2 --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/PinkNoise.java @@ -0,0 +1,128 @@ +/* + * 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.unitgen; + +import com.jsyn.ports.UnitInputPort; +import com.jsyn.ports.UnitOutputPort; +import com.jsyn.util.PseudoRandom; + +/** + * Random output with 3dB per octave rolloff providing a soft natural noise sound. Generated using + * Gardner method. Optimization suggested by James McCartney uses a tree to select which random + * value to replace. + * + *

+ *  x x x x x x x x x x x x x x x x 
+ *  x   x   x   x   x   x   x   x   
+ *  x       x       x       x       
+ *  x               x               
+ *  x
+ * 
+ * + * Tree is generated by counting trailing zeros in an increasing index. When the index is zero, no + * random number is selected. Author: Phil Burk (C) 1996 SoftSynth.com. + */ + +public class PinkNoise extends UnitGenerator implements UnitSource { + + public UnitInputPort amplitude; + public UnitOutputPort output; + + private final int NUM_ROWS = 16; + private final int RANDOM_BITS = 24; + private final int RANDOM_SHIFT = 32 - RANDOM_BITS; + + private PseudoRandom randomNum; + protected double prevNoise, currNoise; + + private long[] rows = new long[NUM_ROWS]; // NEXT RANDOM UNSIGNED 32 + private double scalar; // used to scale within range of -1.0 to +1.0 + private int runningSum; // used to optimize summing of generators + private int index; // incremented with each sample + private int indexMask; // index wrapped and ANDing with this mask + + /* Define Unit Ports used by connect() and set(). */ + public PinkNoise() { + addPort(amplitude = new UnitInputPort("Amplitude", UnitOscillator.DEFAULT_AMPLITUDE)); + addPort(output = new UnitOutputPort("Output")); + + randomNum = new PseudoRandom(); + + // set up for N rows of generators + index = 0; + indexMask = (1 << NUM_ROWS) - 1; + + // Calculate maximum possible signed random value. Extra 1 for white + // noise always added. + int pmax = (NUM_ROWS + 1) * (1 << (RANDOM_BITS - 1)); + scalar = 1.0 / pmax; + + // initialize rows + for (int i = 0; i < NUM_ROWS; i++) { + rows[i] = 0; + } + + runningSum = 0; + } + + @Override + public void generate(int start, int limit) { + double[] amplitudes = amplitude.getValues(); + double[] outputs = output.getValues(); + + for (int i = start; i < limit; i++) { + outputs[i] = generatePinkNoise() * amplitudes[i]; + } + } + + public double generatePinkNoise() { + index = (index + 1) & indexMask; + + // If index is zero, don't update any random values. + if (index != 0) { + // Determine how many trailing zeros in PinkIndex. + // This algorithm will hang of n==0 so test first + int numZeros = 0; + int n = index; + + while ((n & 1) == 0) { + n = n >> 1; + numZeros++; + } + + // Replace the indexed ROWS random value. + // Subtract and add back to RunningSum instead of adding all the + // random values together. Only one changes each time. + runningSum -= rows[numZeros]; + int newRandom = randomNum.nextRandomInteger() >> RANDOM_SHIFT; + runningSum += newRandom; + rows[numZeros] = newRandom; + } + + // Add extra white noise value. + int newRandom = randomNum.nextRandomInteger() >> RANDOM_SHIFT; + int sum = runningSum + newRandom; + + // Scale to range of -1.0 to 0.9999. + return scalar * sum; + } + + @Override + public UnitOutputPort getOutput() { + return output; + } +} diff --git a/src/main/java/com/jsyn/unitgen/PitchDetector.java b/src/main/java/com/jsyn/unitgen/PitchDetector.java new file mode 100644 index 0000000..ff44c93 --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/PitchDetector.java @@ -0,0 +1,120 @@ +/* + * Copyright 2012 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.unitgen; + +import com.jsyn.ports.UnitInputPort; +import com.jsyn.ports.UnitOutputPort; +import com.jsyn.util.AutoCorrelator; +import com.jsyn.util.SignalCorrelator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Estimate the fundamental frequency of a monophonic signal. Analyzes an input signal and outputs + * an estimated period in frames and a frequency in Hertz. The frequency is frameRate/period. The + * confidence tells you how accurate the estimate is. When the confidence is low, you should ignore + * the period. You can use a CompareUnit and a LatchUnit to hold values that you are confident of. + *

+ * Note that a stable monophonic signal is required for accurate pitch tracking. + * + * @author (C) 2012 Phil Burk, Mobileer Inc + */ +public class PitchDetector extends UnitGenerator { + + private static final Logger LOGGER = LoggerFactory.getLogger(PitchDetector.class); + + public UnitInputPort input; + + public UnitOutputPort period; + public UnitOutputPort confidence; + public UnitOutputPort frequency; + public UnitOutputPort updated; + + protected SignalCorrelator signalCorrelator; + + private double lastFrequency = 440.0; + private double lastPeriod = 44100.0 / lastFrequency; // result of analysis TODO update for 48000 + private double lastConfidence = 0.0; // Measure of confidence in the result. + + private static final int LOWEST_FREQUENCY = 40; + private static final int HIGHEST_RATE = 48000; + private static final int CYCLES_NEEDED = 2; + + public PitchDetector() { + super(); + addPort(input = new UnitInputPort("Input")); + + addPort(period = new UnitOutputPort("Period")); + addPort(confidence = new UnitOutputPort("Confidence")); + addPort(frequency = new UnitOutputPort("Frequency")); + addPort(updated = new UnitOutputPort("Updated")); + signalCorrelator = createSignalCorrelator(); + } + + public SignalCorrelator createSignalCorrelator() { + int framesNeeded = HIGHEST_RATE * CYCLES_NEEDED / LOWEST_FREQUENCY; + return new AutoCorrelator(framesNeeded); + } + + @Override + public void generate(int start, int limit) { + double[] inputs = input.getValues(); + double[] periods = period.getValues(); + double[] confidences = confidence.getValues(); + double[] frequencies = frequency.getValues(); + double[] updateds = updated.getValues(); + + for (int i = start; i < limit; i++) { + double current = inputs[i]; + if (signalCorrelator.addSample(current)) { + lastPeriod = signalCorrelator.getPeriod(); + if (lastPeriod < 0.1) { + LOGGER.debug("ILLEGAL PERIOD"); + } + double currentFrequency = getFrameRate() / (lastPeriod + 0); + double confidence = signalCorrelator.getConfidence(); + if (confidence > 0.1) { + if (true) { + double coefficient = confidence * 0.2; + // Take weighted average with previous frequency. + lastFrequency = ((lastFrequency * (1.0 - coefficient)) + (currentFrequency * coefficient)); + } else { + lastFrequency = ((lastFrequency * lastConfidence) + (currentFrequency * confidence)) + / (lastConfidence + confidence); + } + } + lastConfidence = confidence; + updateds[i] = 1.0; + } else { + updateds[i] = 0.0; + } + periods[i] = lastPeriod; + confidences[i] = lastConfidence; + frequencies[i] = lastFrequency; + } + } + + /** + * For debugging only. + * + * @return internal array of correlation results. + */ + public float[] getDiffs() { + return signalCorrelator.getDiffs(); + } + +} diff --git a/src/main/java/com/jsyn/unitgen/PitchToFrequency.java b/src/main/java/com/jsyn/unitgen/PitchToFrequency.java new file mode 100644 index 0000000..9086749 --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/PitchToFrequency.java @@ -0,0 +1,26 @@ +package com.jsyn.unitgen; + +import com.softsynth.math.AudioMath; + +public class PitchToFrequency extends PowerOfTwo { + + public PitchToFrequency() { + input.setup(0.0, 60.0, 127.0); + } + + /** + * Convert from MIDI pitch to an octave offset from Concert A. + */ + @Override + public double adjustInput(double in) { + return (in - AudioMath.CONCERT_A_PITCH) * (1.0/12.0); + } + + /** + * Convert scaler to a frequency relative to Concert A. + */ + @Override + public double adjustOutput(double out) { + return out * AudioMath.getConcertAFrequency(); + } +} diff --git a/src/main/java/com/jsyn/unitgen/PowerOfTwo.java b/src/main/java/com/jsyn/unitgen/PowerOfTwo.java new file mode 100644 index 0000000..5916860 --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/PowerOfTwo.java @@ -0,0 +1,108 @@ +/* + * Copyright 2010 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.unitgen; + +import com.jsyn.ports.UnitInputPort; +import com.jsyn.ports.UnitOutputPort; + +/** + * output = (2.0^input) This is useful for converting a pitch modulation value into a frequency + * scaler. An input value of +1.0 will output 2.0 for an octave increase. An input value of -1.0 + * will output 0.5 for an octave decrease. + * + * This implementation uses a table lookup to optimize for + * speed. It is accurate enough for tuning. It also checks to see if the current input value is the + * same as the previous input value. If so then it reuses the previous computed value. + * + * @author Phil Burk (C) 2010 Mobileer Inc + */ +public class PowerOfTwo extends UnitGenerator { + /** + * Offset in octaves. + */ + public UnitInputPort input; + public UnitOutputPort output; + + private static double[] table; + private static final int NUM_VALUES = 2048; + // Cached computation. + private double lastInput = 0.0; + private double lastOutput = 1.0; + + static { + // Add guard point for faster interpolation. + // Add another point to handle inputs like -1.5308084989341915E-17, + // which generate indices above range. + table = new double[NUM_VALUES + 2]; + // Fill one octave of the table. + for (int i = 0; i < table.length; i++) { + double value = Math.pow(2.0, ((double) i) / NUM_VALUES); + table[i] = value; + } + } + + public PowerOfTwo() { + addPort(input = new UnitInputPort("Input")); + input.setup(-8.0, 0.0, 8.0); + addPort(output = new UnitOutputPort("Output")); + } + + @Override + public void generate(int start, int limit) { + double[] inputs = input.getValues(); + double[] outputs = output.getValues(); + + for (int i = start; i < limit; i++) { + double in = inputs[i]; + // Can we reuse a previously computed value? + if (in == lastInput) { + outputs[i] = lastOutput; + } else { + lastInput = in; + double adjustedInput = adjustInput(in); + int octave = (int) Math.floor(adjustedInput); + double normal = adjustedInput - octave; + // Do table lookup. + double findex = normal * NUM_VALUES; + int index = (int) findex; + double fraction = findex - index; + double value = table[index] + (fraction * (table[index + 1] - table[index])); + + // Adjust for octave. + while (octave > 0) { + octave -= 1; + value *= 2.0; + } + while (octave < 0) { + octave += 1; + value *= 0.5; + } + double adjustedOutput = adjustOutput(value); + outputs[i] = adjustedOutput; + lastOutput = adjustedOutput; + } + } + } + + public double adjustInput(double in) { + return in; + } + + public double adjustOutput(double out) { + return out; + } +} diff --git a/src/main/java/com/jsyn/unitgen/PulseOscillator.java b/src/main/java/com/jsyn/unitgen/PulseOscillator.java new file mode 100644 index 0000000..5ac7352 --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/PulseOscillator.java @@ -0,0 +1,59 @@ +/* + * 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.unitgen; + +import com.jsyn.ports.UnitInputPort; + +/** + * Simple pulse wave oscillator. + * + * @author Phil Burk (C) 2009 Mobileer Inc + */ +public class PulseOscillator extends UnitOscillator { + /** + * Pulse width varies from -1.0 to +1.0. At 0.0 the pulse is actually a square wave. + */ + public UnitInputPort width; + + public PulseOscillator() { + addPort(width = new UnitInputPort("Width")); + } + + @Override + public void generate(int start, int limit) { + double[] frequencies = frequency.getValues(); + double[] amplitudes = amplitude.getValues(); + double[] widths = width.getValues(); + double[] outputs = output.getValues(); + + // Variables have a single value. + double currentPhase = phase.getValue(); + + for (int i = start; i < limit; i++) { + // Generate sawtooth phaser to provide phase for pulse generation. + double phaseIncrement = convertFrequencyToPhaseIncrement(frequencies[i]); + currentPhase = incrementWrapPhase(currentPhase, phaseIncrement); + double ampl = amplitudes[i]; + // Either full negative or positive amplitude. + outputs[i] = (currentPhase < widths[i]) ? -ampl : ampl; + } + + // Value needs to be saved for next time. + phase.setValue(currentPhase); + } + +} diff --git a/src/main/java/com/jsyn/unitgen/PulseOscillatorBL.java b/src/main/java/com/jsyn/unitgen/PulseOscillatorBL.java new file mode 100644 index 0000000..c0e234c --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/PulseOscillatorBL.java @@ -0,0 +1,61 @@ +/* + * 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.unitgen; + +import com.jsyn.engine.MultiTable; +import com.jsyn.ports.UnitInputPort; + +/** + * Pulse oscillator that uses two band limited sawtooth waveforms. + * + * @author Phil Burk (C) 2009 Mobileer Inc + */ +public class PulseOscillatorBL extends SawtoothOscillatorBL { + /** Controls the duty cycle of the pulse waveform. + * The width varies from -1.0 to +1.0. + * When width is zero the output is a square wave. + */ + public UnitInputPort width; + + public PulseOscillatorBL() { + addPort(width = new UnitInputPort("Width")); + } + + @Override + protected double generateBL(MultiTable multiTable, double currentPhase, + double positivePhaseIncrement, double flevel, int i) { + double[] widths = width.getValues(); + double width = widths[i]; + width = (width > 0.999) ? 0.999 : ((width < -0.999) ? -0.999 : width); + + double val1 = multiTable.calculateSawtooth(currentPhase, positivePhaseIncrement, flevel); + + // Generate second sawtooth so we can add them together. + double phase2 = currentPhase + 1.0 - width; // 180 degrees out of phase + if (phase2 >= 1.0) { + phase2 -= 2.0; + } + double val2 = multiTable.calculateSawtooth(phase2, positivePhaseIncrement, flevel); + + /* + * Need to adjust amplitude based on positive phaseInc and width. little less than half at + * Nyquist/2.0! + */ + double scale = 1.0 - positivePhaseIncrement; + return scale * (val1 - val2 - width); + } +} diff --git a/src/main/java/com/jsyn/unitgen/RaisedCosineEnvelope.java b/src/main/java/com/jsyn/unitgen/RaisedCosineEnvelope.java new file mode 100644 index 0000000..c32417c --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/RaisedCosineEnvelope.java @@ -0,0 +1,73 @@ +/* + * Copyright 2011 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.unitgen; + +/** + * An envelope that can be used in a GrainFarm to shape the amplitude of a Grain. The envelope + * starts at 0.0, rises to 1.0, then returns to 0.0 following a cosine curve. + * + *

+ * output = 0.5 - (0.5 * cos(phase))
+ * 
+ * + * @author Phil Burk (C) 2011 Mobileer Inc + * @see GrainFarm + */ +public class RaisedCosineEnvelope extends GrainCommon implements GrainEnvelope { + protected double phase; + protected double phaseIncrement; + + public RaisedCosineEnvelope() { + setFrameRate(44100); + setDuration(0.1); + } + + /** + * @return next value of the envelope. + */ + @Override + public double next() { + phase += phaseIncrement; + if (phase > (2.0 * Math.PI)) { + return 0.0; + } else { + return 0.5 - (0.5 * Math.cos(phase)); // TODO optimize using Taylor expansion + } + } + + /** + * @return true if there are more envelope values left. + */ + @Override + public boolean hasMoreValues() { + return (phase < (2.0 * Math.PI)); + } + + /** + * Reset the envelope back to the beginning. + */ + @Override + public void reset() { + phase = 0.0; + } + + @Override + public void setDuration(double duration) { + phaseIncrement = 2.0 * Math.PI / (getFrameRate() * duration); + } + +} diff --git a/src/main/java/com/jsyn/unitgen/RangeConverter.java b/src/main/java/com/jsyn/unitgen/RangeConverter.java new file mode 100644 index 0000000..ae94b0f --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/RangeConverter.java @@ -0,0 +1,50 @@ +/* + * Copyright 2014 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.unitgen; + +import com.jsyn.ports.UnitInputPort; + +/** + * Convert an input signal between -1.0 and +1.0 to the range min to max. This is handy when using + * an oscillator as a modulator. + * + * @author (C) 2014 Phil Burk, Mobileer Inc + * @see EdgeDetector + */ +public class RangeConverter extends UnitFilter { + public UnitInputPort min; + public UnitInputPort max; + + /* Define Unit Ports used by connect() and set(). */ + public RangeConverter() { + addPort(min = new UnitInputPort("Min", 40.0)); + addPort(max = new UnitInputPort("Max", 2000.0)); + } + + @Override + public void generate(int start, int limit) { + double[] inputs = input.getValues(); + double[] mins = min.getValues(); + double[] maxs = max.getValues(); + double[] outputs = output.getValues(); + + for (int i = start; i < limit; i++) { + double low = mins[i]; + outputs[i] = low + ((maxs[i] - low) * (inputs[i] + 1) * 0.5); + } + } +} diff --git a/src/main/java/com/jsyn/unitgen/RectangularWindow.java b/src/main/java/com/jsyn/unitgen/RectangularWindow.java new file mode 100644 index 0000000..d61f763 --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/RectangularWindow.java @@ -0,0 +1,39 @@ +/* + * Copyright 2013 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.unitgen; + +import com.jsyn.data.SpectralWindow; + +/** + * Window that is just 1.0. Flat like a rectangle. + * + * @author Phil Burk (C) 2013 Mobileer Inc + * @see SpectralFFT + */ +public class RectangularWindow implements SpectralWindow { + static RectangularWindow instance = new RectangularWindow(); + + @Override + /** This always returns 1.0. Do not pass indices outside the window range. */ + public double get(int index) { + return 1.0; // impressive, eh? + } + + public static RectangularWindow getInstance() { + return instance; + } +} diff --git a/src/main/java/com/jsyn/unitgen/RedNoise.java b/src/main/java/com/jsyn/unitgen/RedNoise.java new file mode 100644 index 0000000..d3e4321 --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/RedNoise.java @@ -0,0 +1,80 @@ +/* + * Copyright 1997 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.unitgen; + +import com.jsyn.util.PseudoRandom; + +/** + * RedNoise unit. This unit interpolates straight line segments between pseudo-random numbers to + * produce "red" noise. It is a grittier alternative to the white generator WhiteNoise. It is also + * useful as a slowly changing random control generator for natural sounds. Frequency port controls + * the number of times per second that a new random number is chosen. + * + * @author (C) 1997 Phil Burk, SoftSynth.com + * @see WhiteNoise + */ +public class RedNoise extends UnitOscillator { + private PseudoRandom randomNum; + protected double prevNoise, currNoise; + + /* Define Unit Ports used by connect() and set(). */ + public RedNoise() { + super(); + randomNum = new PseudoRandom(); + } + + @Override + public void generate(int start, int limit) { + double[] amplitudes = amplitude.getValues(); + double[] frequencies = frequency.getValues(); + double[] outputs = output.getValues(); + double currPhase = phase.getValue(); + double phaseIncrement, currOutput; + + double framePeriod = getFramePeriod(); + + for (int i = start; i < limit; i++) { + // compute phase + phaseIncrement = frequencies[i] * framePeriod; + + // verify that phase is within minimums and is not negative + if (phaseIncrement < 0.0) { + phaseIncrement = 0.0 - phaseIncrement; + } + if (phaseIncrement > 1.0) { + phaseIncrement = 1.0; + } + + currPhase += phaseIncrement; + + // calculate new random whenever phase passes 1.0 + if (currPhase > 1.0) { + prevNoise = currNoise; + currNoise = randomNum.nextRandomDouble(); + // reset phase for interpolation + currPhase -= 1.0; + } + + // interpolate current + currOutput = prevNoise + (currPhase * (currNoise - prevNoise)); + outputs[i] = currOutput * amplitudes[i]; + } + + // store new phase + phase.setValue(currPhase); + } +} diff --git a/src/main/java/com/jsyn/unitgen/SampleGrainFarm.java b/src/main/java/com/jsyn/unitgen/SampleGrainFarm.java new file mode 100644 index 0000000..3f908d6 --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/SampleGrainFarm.java @@ -0,0 +1,71 @@ +/* + * Copyright 2011 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.unitgen; + +import com.jsyn.data.FloatSample; +import com.jsyn.ports.UnitInputPort; + +/** + * A GrainFarm that uses a FloatSample as source material. In this example we load a FloatSample for + * use as a source material. + * + *

+	synth.add(sampleGrainFarm = new SampleGrainFarm());
+	// Load a sample that we want to "granulate" from a file.
+	sample = SampleLoader.loadFloatSample(sampleFile);
+	sampleGrainFarm.setSample(sample);
+	// Use a ramp to move smoothly within the file.
+	synth.add(ramp = new ContinuousRamp());
+	ramp.output.connect(sampleGrainFarm.position);
+
+ * + * @author Phil Burk (C) 2011 Mobileer Inc + */ +public class SampleGrainFarm extends GrainFarm { + private FloatSample sample; + public UnitInputPort position; + public UnitInputPort positionRange; + + public SampleGrainFarm() { + super(); + addPort(position = new UnitInputPort("Position", 0.0)); + addPort(positionRange = new UnitInputPort("PositionRange", 0.0)); + } + + @Override + public void allocate(int numGrains) { + Grain[] grainArray = new Grain[numGrains]; + for (int i = 0; i < numGrains; i++) { + Grain grain = new Grain(new SampleGrainSource(), new RaisedCosineEnvelope()); + grainArray[i] = grain; + } + setGrainArray(grainArray); + } + + @Override + public void setupGrain(Grain grain, int i) { + SampleGrainSource sampleGrainSource = (SampleGrainSource) grain.getSource(); + sampleGrainSource.setSample(sample); + sampleGrainSource.setPosition(position.getValues()[i]); + sampleGrainSource.setPositionRange(positionRange.getValues()[i]); + super.setupGrain(grain, i); + } + + public void setSample(FloatSample sample) { + this.sample = sample; + } +} diff --git a/src/main/java/com/jsyn/unitgen/SampleGrainSource.java b/src/main/java/com/jsyn/unitgen/SampleGrainSource.java new file mode 100644 index 0000000..f33817f --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/SampleGrainSource.java @@ -0,0 +1,69 @@ +/* + * 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.unitgen; + +import com.jsyn.data.FloatSample; + +public class SampleGrainSource extends GrainCommon implements GrainSource { + private FloatSample sample; + private double position; // ranges from -1.0 to 1.0 + private double positionRange; + private double phase; // ranges from 0.0 to 1.0 + private double phaseIncrement; + private int numFramesGuarded; + private static final double MAX_PHASE = 0.9999999999; + + @Override + public double next() { + phase += phaseIncrement; + if (phase > MAX_PHASE) { + phase = MAX_PHASE; + } + double fractionalIndex = phase * numFramesGuarded; + return sample.interpolate(fractionalIndex); + } + + @Override + public void setRate(double rate) { + phaseIncrement = rate * sample.getFrameRate() / (getFrameRate() * numFramesGuarded); + } + + public void setSample(FloatSample sample) { + this.sample = sample; + numFramesGuarded = sample.getNumFrames() - 1; + } + + public void setPosition(double position) { + this.position = position; + } + + @Override + public void reset() { + double randomPosition = position + (positionRange * (Math.random() - 0.5)); + phase = (randomPosition * 0.5) + 0.5; + if (phase < 0.0) { + phase = 0.0; + } else if (phase > MAX_PHASE) { + phase = MAX_PHASE; + } + } + + public void setPositionRange(double positionRange) { + this.positionRange = positionRange; + } + +} diff --git a/src/main/java/com/jsyn/unitgen/SawtoothOscillator.java b/src/main/java/com/jsyn/unitgen/SawtoothOscillator.java new file mode 100644 index 0000000..1b3dead --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/SawtoothOscillator.java @@ -0,0 +1,47 @@ +/* + * 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.unitgen; + +/** + * Simple sawtooth oscillator. + * + * @author Phil Burk (C) 2009 Mobileer Inc + */ +public class SawtoothOscillator extends UnitOscillator { + + @Override + public void generate(int start, int limit) { + + double[] frequencies = frequency.getValues(); + double[] amplitudes = amplitude.getValues(); + double[] outputs = output.getValues(); + + // Variables have a single value. + double currentPhase = phase.getValue(); + + for (int i = start; i < limit; i++) { + /* Generate sawtooth phasor to provide phase for sine generation. */ + double phaseIncrement = convertFrequencyToPhaseIncrement(frequencies[i]); + currentPhase = incrementWrapPhase(currentPhase, phaseIncrement); + outputs[i] = currentPhase * amplitudes[i]; + } + + // Value needs to be saved for next time. + phase.setValue(currentPhase); + } + +} diff --git a/src/main/java/com/jsyn/unitgen/SawtoothOscillatorBL.java b/src/main/java/com/jsyn/unitgen/SawtoothOscillatorBL.java new file mode 100644 index 0000000..8b58f6c --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/SawtoothOscillatorBL.java @@ -0,0 +1,65 @@ +/* + * 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.unitgen; + +import com.jsyn.engine.MultiTable; + +/** + * Sawtooth oscillator that uses multiple wave tables for band limiting. This requires more CPU than + * a plain SawtoothOscillator but has less aliasing at high frequencies. + * + * @author Phil Burk (C) 2009 Mobileer Inc + */ +public class SawtoothOscillatorBL extends UnitOscillator { + @Override + public void generate(int start, int limit) { + MultiTable multiTable = MultiTable.getInstance(); + + double[] frequencies = frequency.getValues(); + double[] amplitudes = amplitude.getValues(); + double[] outputs = output.getValues(); + // Variables have a single value. + double currentPhase = phase.getValue(); + + double phaseIncrement = convertFrequencyToPhaseIncrement(frequencies[0]); + double positivePhaseIncrement = Math.abs(phaseIncrement); + // This is very expensive so we moved it outside the loop. + // Try to optimize it with a table lookup. + double flevel = multiTable.convertPhaseIncrementToLevel(positivePhaseIncrement); + + for (int i = start; i < limit; i++) { + /* Generate sawtooth phasor to provide phase for sine generation. */ + phaseIncrement = convertFrequencyToPhaseIncrement(frequencies[i]); + currentPhase = incrementWrapPhase(currentPhase, phaseIncrement); + positivePhaseIncrement = Math.abs(phaseIncrement); + + double val = generateBL(multiTable, currentPhase, positivePhaseIncrement, flevel, i); + + outputs[i] = val * amplitudes[i]; + } + + // Value needs to be saved for next time. + phase.setValue(currentPhase); + } + + protected double generateBL(MultiTable multiTable, double currentPhase, + double positivePhaseIncrement, double flevel, int i) { + /* Calculate table level then use it for lookup. */ + return multiTable.calculateSawtooth(currentPhase, positivePhaseIncrement, flevel); + } + +} diff --git a/src/main/java/com/jsyn/unitgen/SawtoothOscillatorDPW.java b/src/main/java/com/jsyn/unitgen/SawtoothOscillatorDPW.java new file mode 100644 index 0000000..27d0c5a --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/SawtoothOscillatorDPW.java @@ -0,0 +1,76 @@ +/* + * 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.unitgen; + +/** + * Sawtooth DPW oscillator (a sawtooth with reduced aliasing). + * Based on a paper by Antti Huovilainen and Vesa Valimaki: + * http://www.scribd.com/doc/33863143/New-Approaches-to-Digital-Subtractive-Synthesis + * + * @author Phil Burk and Lisa Tolentino (C) 2009 Mobileer Inc + */ +public class SawtoothOscillatorDPW extends UnitOscillator { + // At a very low frequency, switch from DPW to raw sawtooth. + private static final double VERY_LOW_FREQUENCY = 2.0 * 0.1 / 44100.0; + private double z1; + private double z2; + + @Override + public void generate(int start, int limit) { + double[] frequencies = frequency.getValues(); + double[] amplitudes = amplitude.getValues(); + double[] outputs = output.getValues(); + + // Variables have a single value. + double currentPhase = phase.getValue(); + + for (int i = start; i < limit; i++) { + /* Generate raw sawtooth phaser. */ + double phaseIncrement = convertFrequencyToPhaseIncrement(frequencies[i]); + currentPhase = incrementWrapPhase(currentPhase, phaseIncrement); + + /* Square the raw sawtooth. */ + double squared = currentPhase * currentPhase; + // Differentiate using a delayed value. + double diffed = squared - z2; + z2 = z1; + z1 = squared; + + /* Calculate scaling based on phaseIncrement */ + double pinc = phaseIncrement; + // Absolute value. + if (pinc < 0.0) { + pinc = 0.0 - pinc; + } + + double dpw; + // If the frequency is very low then just use the raw sawtooth. + // This avoids divide by zero problems and scaling problems. + if (pinc < VERY_LOW_FREQUENCY) { + dpw = currentPhase; + } else { + dpw = diffed * 0.25 / pinc; + } + + outputs[i] = amplitudes[i] * dpw; + } + + // Value needs to be saved for next time. + phase.setValue(currentPhase); + } + +} diff --git a/src/main/java/com/jsyn/unitgen/SchmidtTrigger.java b/src/main/java/com/jsyn/unitgen/SchmidtTrigger.java new file mode 100644 index 0000000..64129ff --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/SchmidtTrigger.java @@ -0,0 +1,83 @@ +/* + * 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.unitgen; + +import com.jsyn.ports.UnitInputPort; +import com.jsyn.ports.UnitOutputPort; + +/** + * SchmidtTrigger unit. + *

+ * Output logic level value with hysteresis. Transition high when input exceeds setLevel. Only go + * low when input is below resetLevel. This can be used to reject low level noise on the input + * signal. The default values for setLevel and resetLevel are both 0.0. Setting setLevel to 0.1 and + * resetLevel to -0.1 will give some hysteresis. The outputPulse is a single sample wide pulse set + * when the output transitions from low to high. + * + *

+ * if (output == 0.0)
+ *     output = (input > setLevel) ? 1.0 : 0.0;
+ * else if (output > 0.0)
+ *     output = (input <= resetLevel) ? 0.0 : 1.0;
+ * else
+ *     output = previous_output;
+ * 
+ * + * @author Phil Burk (C) 2009 Mobileer Inc + * @see Compare + */ +public class SchmidtTrigger extends UnitFilter { + public UnitInputPort setLevel; + public UnitInputPort resetLevel; + public UnitOutputPort outputPulse; + + /* Define Unit Ports used by connect() and set(). */ + public SchmidtTrigger() { + addPort(setLevel = new UnitInputPort("SetLevel")); + addPort(resetLevel = new UnitInputPort("ResetLevel")); + addPort(input = new UnitInputPort("Input")); + addPort(outputPulse = new UnitOutputPort("OutputPulse")); + } + + @Override + public void generate(int start, int limit) { + double[] inPtr = input.getValues(); + double[] pulsePtr = outputPulse.getValues(); + double[] outPtr = output.getValues(); + double[] setPtr = setLevel.getValues(); + double[] resetPtr = resetLevel.getValues(); + + double outputValue = outPtr[0]; + boolean state = (outputValue > UnitGenerator.FALSE); + for (int i = start; i < limit; i++) { + pulsePtr[i] = UnitGenerator.FALSE; + if (state) { + if (inPtr[i] <= resetPtr[i]) { + state = false; + outputValue = UnitGenerator.FALSE; + } + } else { + if (inPtr[i] > setPtr[i]) { + state = true; + outputValue = UnitGenerator.TRUE; + pulsePtr[i] = UnitGenerator.TRUE; /* Single impulse. */ + } + } + outPtr[i] = outputValue; + } + } +} diff --git a/src/main/java/com/jsyn/unitgen/Select.java b/src/main/java/com/jsyn/unitgen/Select.java new file mode 100644 index 0000000..6d8792e --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/Select.java @@ -0,0 +1,56 @@ +/* + * Copyright 2004 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.unitgen; + +import com.jsyn.ports.UnitInputPort; +import com.jsyn.ports.UnitOutputPort; + +/** + * SelectUnit unit. Select InputA or InputB based on value on Select port. + * + *
 
+   output = ( select > 0.0 ) ? inputB : inputA;
+ 
+ * + * @author (C) 2004-2009 Phil Burk, SoftSynth.com + */ + +public class Select extends UnitGenerator { + public UnitInputPort inputA; + public UnitInputPort inputB; + public UnitInputPort select; + public UnitOutputPort output; + + public Select() { + addPort(inputA = new UnitInputPort("InputA")); + addPort(inputB = new UnitInputPort("InputB")); + addPort(select = new UnitInputPort("Select")); + addPort(output = new UnitOutputPort("Output")); + } + + @Override + public void generate(int start, int limit) { + double[] inputAs = inputA.getValues(); + double[] inputBs = inputB.getValues(); + double[] selects = select.getValues(); + double[] outputs = output.getValues(); + + for (int i = start; i < limit; i++) { + outputs[i] = (selects[i] > UnitGenerator.FALSE) ? inputBs[i] : inputAs[i]; + } + } +} diff --git a/src/main/java/com/jsyn/unitgen/SequentialDataReader.java b/src/main/java/com/jsyn/unitgen/SequentialDataReader.java new file mode 100644 index 0000000..901767b --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/SequentialDataReader.java @@ -0,0 +1,38 @@ +/* + * 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.unitgen; + +import com.jsyn.ports.UnitDataQueuePort; +import com.jsyn.ports.UnitInputPort; +import com.jsyn.ports.UnitOutputPort; + +/** + * Base class for reading a sample or envelope. + * + * @author Phil Burk (C) 2009 Mobileer Inc + */ +public abstract class SequentialDataReader extends UnitGenerator { + public UnitDataQueuePort dataQueue; + public UnitInputPort amplitude; + public UnitOutputPort output; + + /* Define Unit Ports used by connect() and set(). */ + public SequentialDataReader() { + addPort(dataQueue = new UnitDataQueuePort("Data")); + addPort(amplitude = new UnitInputPort("Amplitude", UnitOscillator.DEFAULT_AMPLITUDE)); + } +} diff --git a/src/main/java/com/jsyn/unitgen/SequentialDataWriter.java b/src/main/java/com/jsyn/unitgen/SequentialDataWriter.java new file mode 100644 index 0000000..cb3bb11 --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/SequentialDataWriter.java @@ -0,0 +1,44 @@ +/* + * 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.unitgen; + +import com.jsyn.ports.UnitDataQueuePort; +import com.jsyn.ports.UnitInputPort; + +/** + * Base class for writing to a sample. + * + * Note that you must call start() on subclasses of this unit because it does not have an output for pulling data. + * + * @author Phil Burk (C) 2009 Mobileer Inc + */ +public abstract class SequentialDataWriter extends UnitGenerator { + public UnitDataQueuePort dataQueue; + public UnitInputPort input; + + public SequentialDataWriter() { + addPort(dataQueue = new UnitDataQueuePort("Data")); + } + + /** + * This unit won't do anything unless you start() it. + */ + @Override + public boolean isStartRequired() { + return true; + } +} diff --git a/src/main/java/com/jsyn/unitgen/SineOscillator.java b/src/main/java/com/jsyn/unitgen/SineOscillator.java new file mode 100644 index 0000000..8112e46 --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/SineOscillator.java @@ -0,0 +1,84 @@ +/* + * 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.unitgen; + +/** + * Sine oscillator generates a frequency controlled sine wave. It is implemented using a fast Taylor + * expansion. + * + * @author Phil Burk (C) 2009 Mobileer Inc + */ +public class SineOscillator extends UnitOscillator { + public SineOscillator() { + } + + public SineOscillator(double freq) { + frequency.set(freq); + } + + public SineOscillator(double freq, double amp) { + frequency.set(freq); + amplitude.set(amp); + } + + @Override + public void generate(int start, int limit) { + double[] frequencies = frequency.getValues(); + double[] amplitudes = amplitude.getValues(); + double[] outputs = output.getValues(); + double currentPhase = phase.getValue(); + + for (int i = start; i < limit; i++) { + /* Generate sawtooth phasor to provide phase for sine generation. */ + double phaseIncrement = convertFrequencyToPhaseIncrement(frequencies[i]); + currentPhase = incrementWrapPhase(currentPhase, phaseIncrement); + if (true) { + double value = fastSin(currentPhase); + outputs[i] = value * amplitudes[i]; + } else { + // Slower but more accurate implementation. + outputs[i] = Math.sin(currentPhase * Math.PI) * amplitudes[i]; + } + } + + phase.setValue(currentPhase); + } + + /** + * Calculate sine using Taylor expansion. Do not use values outside the range. + * + * @param currentPhase in the range of -1.0 to +1.0 for one cycle + */ + public static double fastSin(double currentPhase) { + // Factorial constants so code is easier to read. + final double IF3 = 1.0 / (2 * 3); + final double IF5 = IF3 / (4 * 5); + final double IF7 = IF5 / (6 * 7); + final double IF9 = IF7 / (8 * 9); + final double IF11 = IF9 / (10 * 11); + + /* Wrap phase back into region where results are more accurate. */ + double yp = (currentPhase > 0.5) ? 1.0 - currentPhase : ((currentPhase < (-0.5)) ? (-1.0) + - currentPhase : currentPhase); + + double x = yp * Math.PI; + double x2 = (x * x); + /* Taylor expansion out to x**11/11! factored into multiply-adds */ + return x + * (x2 * (x2 * (x2 * (x2 * ((x2 * (-IF11)) + IF9) - IF7) + IF5) - IF3) + 1); + } +} diff --git a/src/main/java/com/jsyn/unitgen/SineOscillatorPhaseModulated.java b/src/main/java/com/jsyn/unitgen/SineOscillatorPhaseModulated.java new file mode 100644 index 0000000..7631dff --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/SineOscillatorPhaseModulated.java @@ -0,0 +1,74 @@ +/* + * 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.unitgen; + +import com.jsyn.ports.UnitInputPort; + +/** + * Sine oscillator with a phase modulation input. Phase modulation is similar to frequency + * modulation but is easier to use in some ways. + * + *
+ * output = sin(PI * (phase + modulation))
+ * 
+ * + * @author Phil Burk (C) 2009 Mobileer Inc + */ +public class SineOscillatorPhaseModulated extends SineOscillator { + public UnitInputPort modulation; + + /* Define Unit Ports used by connect() and set(). */ + public SineOscillatorPhaseModulated() { + super(); + addPort(modulation = new UnitInputPort("Modulation")); + } + + @Override + public void generate(int start, int limit) { + double[] frequencies = frequency.getValues(); + double[] amplitudes = amplitude.getValues(); + double[] outputs = output.getValues(); + double[] modulations = modulation.getValues(); + double currentPhase = phase.getValue(); + + for (int i = start; i < limit; i++) { + /* Generate sawtooth phaser to provide phase for sine generation. */ + double phaseIncrement = convertFrequencyToPhaseIncrement(frequencies[i]); + currentPhase = incrementWrapPhase(currentPhase, phaseIncrement); + double modulatedPhase = currentPhase + modulations[i]; + double value; + if (false) { + // TODO Compare benchmarks. + while (modulatedPhase >= 1.0) { + modulatedPhase -= 2.0; + } + while (modulatedPhase < -1.0) { + modulatedPhase += 2.0; + } + value = fastSin(modulatedPhase); + } else { + value = Math.sin(modulatedPhase * Math.PI); + } + outputs[i] = value * amplitudes[i]; + // System.out.format("Sine: freq = %10.4f , amp = %8.5f, out = %8.5f, phase = %8.5f, frame = %8d\n", + // frequencies[i], amplitudes[i],outputs[i],currentPhase,frame++ ); + } + + phase.setValue(currentPhase); + } + +} diff --git a/src/main/java/com/jsyn/unitgen/SpectralFFT.java b/src/main/java/com/jsyn/unitgen/SpectralFFT.java new file mode 100644 index 0000000..f3e881a --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/SpectralFFT.java @@ -0,0 +1,130 @@ +/* + * Copyright 2013 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.unitgen; + +import java.util.Arrays; + +import com.jsyn.data.SpectralWindow; +import com.jsyn.data.Spectrum; +import com.jsyn.ports.UnitInputPort; +import com.jsyn.ports.UnitSpectralOutputPort; +import com.softsynth.math.FourierMath; + +/** + * Periodically transform the input signal using an FFT. Output complete spectra. + * + * @author Phil Burk (C) 2013 Mobileer Inc + * @version 016 + * @see SpectralIFFT + * @see Spectrum + * @see SpectralFilter + */ +public class SpectralFFT extends UnitGenerator { + public UnitInputPort input; + /** + * Provides complete complex spectra when the FFT completes. + */ + public UnitSpectralOutputPort output; + private double[] buffer; + private int cursor; + private SpectralWindow window = RectangularWindow.getInstance(); + private int sizeLog2; + private int offset; + private boolean running; + + /* Define Unit Ports used by connect() and set(). */ + public SpectralFFT() { + this(Spectrum.DEFAULT_SIZE_LOG_2); + } + + /** + * @param sizeLog2 for example, pass 10 to get a 1024 bin FFT + */ + public SpectralFFT(int sizeLog2) { + addPort(input = new UnitInputPort("Input")); + addPort(output = new UnitSpectralOutputPort("Output", 1 << sizeLog2)); + setSizeLog2(sizeLog2); + } + + /** + * Please do not change the size of the FFT while JSyn is running. + * + * @param sizeLog2 for example, pass 9 to get a 512 bin FFT + */ + public void setSizeLog2(int sizeLog2) { + this.sizeLog2 = sizeLog2; + output.setSize(1 << sizeLog2); + buffer = output.getSpectrum().getReal(); + cursor = 0; + } + + public int getSizeLog2() { + return sizeLog2; + } + + @Override + public void generate(int start, int limit) { + if (!running) { + int mask = (1 << sizeLog2) - 1; + if (((getSynthesisEngine().getFrameCount() - offset) & mask) == 0) { + running = true; + cursor = 0; + } + } + // Don't use "else" because "running" may have changed in above block. + if (running) { + double[] inputs = input.getValues(); + for (int i = start; i < limit; i++) { + buffer[cursor] = inputs[i] * window.get(cursor); + ++cursor; + // When it is full, do the FFT. + if (cursor == buffer.length) { + Spectrum spectrum = output.getSpectrum(); + Arrays.fill(spectrum.getImaginary(), 0.0); + FourierMath.fft(buffer.length, spectrum.getReal(), spectrum.getImaginary()); + output.advance(); + cursor = 0; + } + } + } + } + + public SpectralWindow getWindow() { + return window; + } + + /** + * Multiply input data by this window before doing the FFT. The default is a RectangularWindow. + */ + public void setWindow(SpectralWindow window) { + this.window = window; + } + + /** + * The FFT will be performed on a frame that is a multiple of the size plus this offset. + * + * @param offset + */ + public void setOffset(int offset) { + this.offset = offset; + } + + public int getOffset() { + return offset; + } + +} diff --git a/src/main/java/com/jsyn/unitgen/SpectralFilter.java b/src/main/java/com/jsyn/unitgen/SpectralFilter.java new file mode 100644 index 0000000..758c8e7 --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/SpectralFilter.java @@ -0,0 +1,130 @@ +/* + * Copyright 2014 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.unitgen; + +import com.jsyn.data.SpectralWindow; +import com.jsyn.data.SpectralWindowFactory; +import com.jsyn.data.Spectrum; +import com.jsyn.ports.UnitInputPort; +import com.jsyn.ports.UnitOutputPort; +import com.jsyn.ports.UnitSpectralInputPort; +import com.jsyn.ports.UnitSpectralOutputPort; + +/** + * Process a signal using multiple overlapping FFT and IFFT pairs. For passthrough, you can connect + * the spectral outputs to the spectral inputs. Or you can connect one or more SpectralProcessors + * between them. + * + *
+ * for (int i = 0; i < numFFTs; i++) {
+ *     filter.getSpectralOutput(i).connect(processors[i].input);
+ *     processors[i].output.connect(filter.getSpectralInput(i));
+ * }
+ * 
+ * + * See the example program "HearSpectralFilter.java". Note that this spectral API is experimental + * and may change at any time. + * + * @author Phil Burk (C) 2014 Mobileer Inc + * @see SpectralProcessor + */ +public class SpectralFilter extends Circuit implements UnitSink, UnitSource { + public UnitInputPort input; + public UnitOutputPort output; + + private SpectralFFT[] ffts; + private SpectralIFFT[] iffts; + private PassThrough inlet; // fan out to FFTs + private PassThrough sum; // mix output of IFFTs + + /** + * Create a default sized filter with 2 FFT/IFFT pairs and a sizeLog2 of + * Spectrum.DEFAULT_SIZE_LOG_2. + */ + public SpectralFilter() { + this(2, Spectrum.DEFAULT_SIZE_LOG_2); + } + + /** + * @param numFFTs number of FFT/IFFT pairs for the overlap and add + * @param sizeLog2 for example, use 10 to get a 1024 bin FFT, 12 for 4096 + */ + public SpectralFilter(int numFFTs, int sizeLog2) { + add(inlet = new PassThrough()); + add(sum = new PassThrough()); + ffts = new SpectralFFT[numFFTs]; + iffts = new SpectralIFFT[numFFTs]; + int offset = (1 << sizeLog2) / numFFTs; + for (int i = 0; i < numFFTs; i++) { + add(ffts[i] = new SpectralFFT(sizeLog2)); + inlet.output.connect(ffts[i].input); + ffts[i].setOffset(i * offset); + + add(iffts[i] = new SpectralIFFT()); + iffts[i].output.connect(sum.input); + } + setWindow(SpectralWindowFactory.getHammingWindow(sizeLog2)); + + addPort(input = inlet.input); + addPort(output = sum.output); + } + + public SpectralWindow getWindow() { + return ffts[0].getWindow(); + } + + /** + * Specify one window to be used for all FFTs and IFFTs. The window should be the same size as + * the FFTs. + * + * @param window default is HammingWindow + * @see SpectralWindowFactory + */ + public void setWindow(SpectralWindow window) { + // Use the same window everywhere. + for (int i = 0; i < ffts.length; i++) { + ffts[i].setWindow(window); // TODO review, both sides or just one + iffts[i].setWindow(window); + } + } + + @Override + public UnitOutputPort getOutput() { + return output; + } + + @Override + public UnitInputPort getInput() { + return input; + } + + /** + * @param i + * @return the output of the indexed FFT + */ + public UnitSpectralOutputPort getSpectralOutput(int i) { + return ffts[i].output; + } + + /** + * @param i + * @return the input of the indexed IFFT + */ + public UnitSpectralInputPort getSpectralInput(int i) { + return iffts[i].input; + } +} diff --git a/src/main/java/com/jsyn/unitgen/SpectralIFFT.java b/src/main/java/com/jsyn/unitgen/SpectralIFFT.java new file mode 100644 index 0000000..c040e52 --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/SpectralIFFT.java @@ -0,0 +1,92 @@ +/* + * Copyright 2013 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.unitgen; + +import com.jsyn.data.SpectralWindow; +import com.jsyn.data.Spectrum; +import com.jsyn.ports.UnitOutputPort; +import com.jsyn.ports.UnitSpectralInputPort; +import com.softsynth.math.FourierMath; + +/** + * Periodically transform the input signal using an Inverse FFT. + * + * @author Phil Burk (C) 2013 Mobileer Inc + * @version 016 + * @see SpectralFFT + */ +public class SpectralIFFT extends UnitGenerator { + public UnitSpectralInputPort input; + public UnitOutputPort output; + private Spectrum localSpectrum; + private double[] buffer; + private int cursor; + private SpectralWindow window = RectangularWindow.getInstance(); + + /* Define Unit Ports used by connect() and set(). */ + public SpectralIFFT() { + addPort(output = new UnitOutputPort()); + addPort(input = new UnitSpectralInputPort("Input")); + } + + @Override + public void generate(int start, int limit) { + double[] outputs = output.getValues(); + + if (buffer == null) { + if (input.isAvailable()) { + Spectrum spectrum = input.getSpectrum(); + int size = spectrum.size(); + localSpectrum = new Spectrum(size); + buffer = localSpectrum.getReal(); + cursor = 0; + } else { + for (int i = start; i < limit; i++) { + outputs[i] = 0.0; + } + } + } + + if (buffer != null) { + for (int i = start; i < limit; i++) { + if (cursor == 0) { + Spectrum spectrum = input.getSpectrum(); + spectrum.copyTo(localSpectrum); + FourierMath.ifft(buffer.length, localSpectrum.getReal(), + localSpectrum.getImaginary()); + } + + outputs[i] = buffer[cursor] * window.get(cursor); + cursor += 1; + if (cursor == buffer.length) { + cursor = 0; + } + } + } + } + + public SpectralWindow getWindow() { + return window; + } + + /** + * Multiply output data by this window after doing the FFT. The default is a RectangularWindow. + */ + public void setWindow(SpectralWindow window) { + this.window = window; + } +} diff --git a/src/main/java/com/jsyn/unitgen/SpectralProcessor.java b/src/main/java/com/jsyn/unitgen/SpectralProcessor.java new file mode 100644 index 0000000..de96877 --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/SpectralProcessor.java @@ -0,0 +1,73 @@ +/* + * Copyright 2014 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.unitgen; + +import com.jsyn.data.Spectrum; +import com.jsyn.ports.UnitSpectralInputPort; +import com.jsyn.ports.UnitSpectralOutputPort; + +/** + * This is a base class for implementing your own spectral processing units. You need to implement + * the processSpectrum() method. + * + * @author Phil Burk (C) 2014 Mobileer Inc + * @see Spectrum + */ +public abstract class SpectralProcessor extends UnitGenerator { + public UnitSpectralInputPort input; + public UnitSpectralOutputPort output; + private int counter; + + /* Define Unit Ports used by connect() and set(). */ + public SpectralProcessor() { + addPort(output = new UnitSpectralOutputPort()); + addPort(input = new UnitSpectralInputPort()); + } + + /* Define Unit Ports used by connect() and set(). */ + public SpectralProcessor(int size) { + addPort(output = new UnitSpectralOutputPort(size)); + addPort(input = new UnitSpectralInputPort()); + } + + @Override + public void generate(int start, int limit) { + for (int i = start; i < limit; i++) { + if (counter == 0) { + if (input.isAvailable()) { + Spectrum inputSpectrum = input.getSpectrum(); + Spectrum outputSpectrum = output.getSpectrum(); + processSpectrum(inputSpectrum, outputSpectrum); + + output.advance(); + counter = inputSpectrum.size() - 1; + } + } else { + counter--; + } + } + } + + /** + * Define this method to implement your own processor. + * + * @param inputSpectrum + * @param outputSpectrum + */ + public abstract void processSpectrum(Spectrum inputSpectrum, Spectrum outputSpectrum); + +} diff --git a/src/main/java/com/jsyn/unitgen/SquareOscillator.java b/src/main/java/com/jsyn/unitgen/SquareOscillator.java new file mode 100644 index 0000000..aaca2d0 --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/SquareOscillator.java @@ -0,0 +1,49 @@ +/* + * 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.unitgen; + +/** + * Simple square wave oscillator. + * + * @author Phil Burk (C) 2009 Mobileer Inc + */ +public class SquareOscillator extends UnitOscillator { + + @Override + public void generate(int start, int limit) { + double[] frequencies = frequency.getValues(); + double[] amplitudes = amplitude.getValues(); + double[] outputs = output.getValues(); + + // Variables have a single value. + double currentPhase = phase.getValue(); + + for (int i = start; i < limit; i++) { + /* Generate sawtooth phasor to provide phase for square generation. */ + double phaseIncrement = convertFrequencyToPhaseIncrement(frequencies[i]); + currentPhase = incrementWrapPhase(currentPhase, phaseIncrement); + + double ampl = amplitudes[i]; + // Either full negative or positive amplitude. + outputs[i] = (currentPhase < 0.0) ? -ampl : ampl; + } + + // Value needs to be saved for next time. + phase.setValue(currentPhase); + } + +} diff --git a/src/main/java/com/jsyn/unitgen/SquareOscillatorBL.java b/src/main/java/com/jsyn/unitgen/SquareOscillatorBL.java new file mode 100644 index 0000000..cb9e141 --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/SquareOscillatorBL.java @@ -0,0 +1,48 @@ +/* + * 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.unitgen; + +import com.jsyn.engine.MultiTable; + +/** + * Band-limited square wave oscillator. This requires more CPU than a SquareOscillator but is less + * noisy at high frequencies. + * + * @author Phil Burk (C) 2009 Mobileer Inc + */ +public class SquareOscillatorBL extends SawtoothOscillatorBL { + @Override + protected double generateBL(MultiTable multiTable, double currentPhase, + double positivePhaseIncrement, double flevel, int i) { + double val1 = multiTable.calculateSawtooth(currentPhase, positivePhaseIncrement, flevel); + + /* Generate second sawtooth so we can add them together. */ + double phase2 = currentPhase + 1.0; /* 180 degrees out of phase. */ + if (phase2 >= 1.0) { + phase2 -= 2.0; + } + double val2 = multiTable.calculateSawtooth(phase2, positivePhaseIncrement, flevel); + + /* + * Need to adjust amplitude based on positive phaseInc. little less than half at + * Nyquist/2.0! + */ + final double STARTAMP = 0.92; /* Derived by viewing waveforms with TJ_SEEOSC */ + double scale = STARTAMP - positivePhaseIncrement; + return scale * (val1 - val2); + } +} diff --git a/src/main/java/com/jsyn/unitgen/StereoStreamWriter.java b/src/main/java/com/jsyn/unitgen/StereoStreamWriter.java new file mode 100644 index 0000000..b387836 --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/StereoStreamWriter.java @@ -0,0 +1,53 @@ +/* + * 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.unitgen; + +import java.io.IOException; + +import com.jsyn.io.AudioOutputStream; +import com.jsyn.ports.UnitInputPort; + +/** + * Write two samples per audio frame to an AudioOutputStream as interleaved samples. + * + * Note that you must call start() on this unit because it does not have an output for pulling data. + * + * @author Phil Burk (C) 2009 Mobileer Inc + */ +public class StereoStreamWriter extends UnitStreamWriter { + public StereoStreamWriter() { + addPort(input = new UnitInputPort(2, "Input")); + } + + @Override + public void generate(int start, int limit) { + double[] leftInputs = input.getValues(0); + double[] rightInputs = input.getValues(1); + AudioOutputStream output = outputStream; + if (output != null) { + try { + for (int i = start; i < limit; i++) { + output.write(leftInputs[i]); + output.write(rightInputs[i]); + } + } catch (IOException e) { + e.printStackTrace(); + output = null; + } + } + } +} diff --git a/src/main/java/com/jsyn/unitgen/StochasticGrainScheduler.java b/src/main/java/com/jsyn/unitgen/StochasticGrainScheduler.java new file mode 100644 index 0000000..1f79877 --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/StochasticGrainScheduler.java @@ -0,0 +1,43 @@ +/* + * Copyright 2011 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.unitgen; + +import com.jsyn.util.PseudoRandom; + +/** + * Use a random function to schedule grains. + * + * @author Phil Burk (C) 2011 Mobileer Inc + */ +public class StochasticGrainScheduler implements GrainScheduler { + PseudoRandom pseudoRandom = new PseudoRandom(); + + @Override + public double nextDuration(double duration) { + return duration; + } + + @Override + public double nextGap(double duration, double density) { + if (density < 0.00000001) { + density = 0.00000001; + } + double gapRange = duration * (1.0 - density) / density; + return pseudoRandom.random() * gapRange; + } + +} diff --git a/src/main/java/com/jsyn/unitgen/Subtract.java b/src/main/java/com/jsyn/unitgen/Subtract.java new file mode 100644 index 0000000..d9ca035 --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/Subtract.java @@ -0,0 +1,42 @@ +/* + * 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.unitgen; + +/** + * This unit performs a signed subtraction on its two inputs. + * + *
+ * output = inputA - inputB
+ * 
+ * + * @author Phil Burk (C) 2009 Mobileer Inc + * @version 016 + * @see MultiplyAdd + * @see Subtract + */ +public class Subtract extends UnitBinaryOperator { + @Override + public void generate(int start, int limit) { + double[] aValues = inputA.getValues(); + double[] bValues = inputB.getValues(); + double[] outputs = output.getValues(); + + for (int i = start; i < limit; i++) { + outputs[i] = aValues[i] - bValues[i]; + } + } +} diff --git a/src/main/java/com/jsyn/unitgen/TriangleOscillator.java b/src/main/java/com/jsyn/unitgen/TriangleOscillator.java new file mode 100644 index 0000000..ada2d6e --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/TriangleOscillator.java @@ -0,0 +1,59 @@ +/* + * 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.unitgen; + +/** + * Simple triangle wave oscillator. + * + * @author Phil Burk (C) 2009 Mobileer Inc + */ +public class TriangleOscillator extends UnitOscillator { + int frame; + + public TriangleOscillator() { + super(); + phase.setValue(-0.5); + } + + @Override + public void generate(int start, int limit) { + + double[] frequencies = frequency.getValues(); + double[] amplitudes = amplitude.getValues(); + double[] outputs = output.getValues(); + + // Variables have a single value. + double currentPhase = phase.getValue(); + + for (int i = start; i < limit; i++) { + /* Generate sawtooth phasor to provide phase for triangle generation. */ + double phaseIncrement = convertFrequencyToPhaseIncrement(frequencies[i]); + currentPhase = incrementWrapPhase(currentPhase, phaseIncrement); + + /* Map phase to triangle waveform. */ + /* 0 - 0.999 => 0.5-p => +0.5 - -0.5 */ + /* -1.0 - 0.0 => 0.5+p => -0.5 - +0.5 */ + double triangle = (currentPhase >= 0.0) ? (0.5 - currentPhase) : (0.5 + currentPhase); + + outputs[i] = triangle * 2.0 * amplitudes[i]; + } + + // Value needs to be saved for next time. + phase.setValue(currentPhase); + } + +} diff --git a/src/main/java/com/jsyn/unitgen/TunableFilter.java b/src/main/java/com/jsyn/unitgen/TunableFilter.java new file mode 100644 index 0000000..d2c9f66 --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/TunableFilter.java @@ -0,0 +1,41 @@ +/* + * 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. + */ +/** + * Aug 26, 2009 + * com.jsyn.engine.units.TunableFilter.java + */ + +package com.jsyn.unitgen; + +import com.jsyn.ports.UnitInputPort; + +/** + * A UnitFilter with a frequency port. + * + * @author Phil Burk (C) 2009 Mobileer Inc Translated from 'C' to Java by Lisa + * Tolentino. + */ +public abstract class TunableFilter extends UnitFilter { + + static final double DEFAULT_FREQUENCY = 400; + public UnitInputPort frequency; + + public TunableFilter() { + addPort(frequency = new UnitInputPort("Frequency")); + frequency.setup(40.0, DEFAULT_FREQUENCY, 6000.0); + } + +} diff --git a/src/main/java/com/jsyn/unitgen/TwoInDualOut.java b/src/main/java/com/jsyn/unitgen/TwoInDualOut.java new file mode 100644 index 0000000..a8fea48 --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/TwoInDualOut.java @@ -0,0 +1,56 @@ +/* + * Copyright 2004 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.unitgen; + +import com.jsyn.ports.UnitInputPort; +import com.jsyn.ports.UnitOutputPort; + +/** + * This unit combines two discrete inputs into a dual (stereo) output. + * + *
+ *  output[0] = inputA
+ *  output[1] = inputB
+ * 
+ * + * @author (C) 2004-2009 Phil Burk, SoftSynth.com + */ + +public class TwoInDualOut extends UnitGenerator { + public UnitInputPort inputA; + public UnitInputPort inputB; + public UnitOutputPort output; + + public TwoInDualOut() { + addPort(inputA = new UnitInputPort("InputA")); + addPort(inputB = new UnitInputPort("InputB")); + addPort(output = new UnitOutputPort(2, "OutputB")); + } + + @Override + public void generate(int start, int limit) { + double[] inputAs = inputA.getValues(); + double[] inputBs = inputB.getValues(); + double[] output0s = output.getValues(0); + double[] output1s = output.getValues(1); + + for (int i = start; i < limit; i++) { + output0s[i] = inputAs[i]; + output1s[i] = inputBs[i]; + } + } +} diff --git a/src/main/java/com/jsyn/unitgen/UnitBinaryOperator.java b/src/main/java/com/jsyn/unitgen/UnitBinaryOperator.java new file mode 100644 index 0000000..c5675ff --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/UnitBinaryOperator.java @@ -0,0 +1,41 @@ +/* + * Copyright 2010 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.unitgen; + +import com.jsyn.ports.UnitInputPort; +import com.jsyn.ports.UnitOutputPort; + +/** + * Base class for binary arithmetic operators like Add and Compare. + * + * @author Phil Burk (C) 2010 Mobileer Inc + */ +public abstract class UnitBinaryOperator extends UnitGenerator { + public UnitInputPort inputA; + public UnitInputPort inputB; + public UnitOutputPort output; + + /* Define Unit Ports used by connect() and set(). */ + public UnitBinaryOperator() { + addPort(inputA = new UnitInputPort("InputA")); + addPort(inputB = new UnitInputPort("InputB")); + addPort(output = new UnitOutputPort("Output")); + } + + @Override + public abstract void generate(int start, int limit); +} diff --git a/src/main/java/com/jsyn/unitgen/UnitFilter.java b/src/main/java/com/jsyn/unitgen/UnitFilter.java new file mode 100644 index 0000000..49976ba --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/UnitFilter.java @@ -0,0 +1,47 @@ +/* + * 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.unitgen; + +import com.jsyn.ports.UnitInputPort; +import com.jsyn.ports.UnitOutputPort; + +/** + * Base class for all filters. + * + * @author Phil Burk (C) 2009 Mobileer Inc + */ +public abstract class UnitFilter extends UnitGenerator implements UnitSink, UnitSource { + public UnitInputPort input; + public UnitOutputPort output; + + /* Define Unit Ports used by connect() and set(). */ + public UnitFilter() { + addPort(input = new UnitInputPort("Input")); + addPort(output = new UnitOutputPort("Output")); + } + + @Override + public UnitInputPort getInput() { + return input; + } + + @Override + public UnitOutputPort getOutput() { + return output; + } + +} diff --git a/src/main/java/com/jsyn/unitgen/UnitGate.java b/src/main/java/com/jsyn/unitgen/UnitGate.java new file mode 100644 index 0000000..59144c2 --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/UnitGate.java @@ -0,0 +1,54 @@ +/* + * Copyright 2012 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.unitgen; + +import com.jsyn.ports.UnitGatePort; +import com.jsyn.ports.UnitOutputPort; + +/** + * Base class for other envelopes. + * + * @author Phil Burk (C) 2012 Mobileer Inc + */ +public abstract class UnitGate extends UnitGenerator implements UnitSource { + /** + * Input that triggers the envelope. Use amplitude port if you want to connect a signal to be + * modulated by the envelope. + */ + public UnitGatePort input; + public UnitOutputPort output; + + public UnitGate() { + addPort(input = new UnitGatePort("Input")); + addPort(output = new UnitOutputPort("Output")); + } + + @Override + public UnitOutputPort getOutput() { + return output; + } + + /** + * Specify a unit to be disabled when the envelope finishes. + * + * @param unit + */ + public void setupAutoDisable(UnitGenerator unit) { + input.setupAutoDisable(unit); + } + +} diff --git a/src/main/java/com/jsyn/unitgen/UnitGenerator.java b/src/main/java/com/jsyn/unitgen/UnitGenerator.java new file mode 100644 index 0000000..7aacaf1 --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/UnitGenerator.java @@ -0,0 +1,357 @@ +/* + * 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.unitgen; + +import java.io.PrintStream; +import java.util.Collection; +import java.util.LinkedHashMap; + +import com.jsyn.Synthesizer; +import com.jsyn.engine.SynthesisEngine; +import com.jsyn.ports.ConnectableInput; +import com.jsyn.ports.UnitInputPort; +import com.jsyn.ports.UnitOutputPort; +import com.jsyn.ports.UnitPort; +import com.softsynth.shared.time.TimeStamp; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Base class for all unit generators. + * + * @author Phil Burk (C) 2009 Mobileer Inc + */ +public abstract class UnitGenerator { + + private static final Logger LOGGER = LoggerFactory.getLogger(UnitGenerator.class); + + protected static final double VERY_SMALL_FLOAT = 1.0e-26; + + // Some common port names. + public static final String PORT_NAME_INPUT = "Input"; + public static final String PORT_NAME_OUTPUT = "Output"; + public static final String PORT_NAME_PHASE = "Phase"; + public static final String PORT_NAME_FREQUENCY = "Frequency"; + public static final String PORT_NAME_FREQUENCY_SCALER = "FreqScaler"; + public static final String PORT_NAME_AMPLITUDE = "Amplitude"; + public static final String PORT_NAME_PAN = "Pan"; + public static final String PORT_NAME_TIME = "Time"; + public static final String PORT_NAME_CUTOFF = "Cutoff"; + public static final String PORT_NAME_PRESSURE = "Pressure"; + public static final String PORT_NAME_TIMBRE = "Timbre"; + + public static final double FALSE = 0.0; + public static final double TRUE = 1.0; + protected SynthesisEngine synthesisEngine; + private final LinkedHashMap ports = new LinkedHashMap(); + private Circuit circuit; + private long lastFrameCount; + private boolean enabled = true; + private static int nextId; + private final int id = nextId++; + + public int getId() { + return id; + } + + public int getFrameRate() { + // return frameRate; + return synthesisEngine.getFrameRate(); + } + + public double getFramePeriod() { + // return framePeriod; // TODO - Why does OldJSynTestSuite fail if I use this! + return synthesisEngine.getFramePeriod(); + } + + public void addPort(UnitPort port) { + port.setUnitGenerator(this); + // Store in a hash table by name. + ports.put(port.getName().toLowerCase(), port); + } + + public void addPort(UnitPort port, String name) { + port.setName(name); + addPort(port); + } + + /** + * Case-insensitive search for a port by name. + * @param portName + * @return matching port or null + */ + public UnitPort getPortByName(String portName) { + return ports.get(portName.toLowerCase()); + } + + public Collection getPorts() { + return ports.values(); + } + + /** + * Perform essential synthesis function. + * + * @param start offset into port buffers + * @param limit limit offset into port buffers for loop + */ + public abstract void generate(int start, int limit); + + /** + * Generate a full block. + */ + public void generate() { + generate(0, Synthesizer.FRAMES_PER_BLOCK); + } + + /** + * @return the synthesisEngine + */ + public SynthesisEngine getSynthesisEngine() { + return synthesisEngine; + } + + /** + * @return the Synthesizer + */ + public Synthesizer getSynthesizer() { + return synthesisEngine; + } + + /** + * @param synthesisEngine the synthesisEngine to set + */ + public void setSynthesisEngine(SynthesisEngine synthesisEngine) { + if ((this.synthesisEngine != null) && (this.synthesisEngine != synthesisEngine)) { + throw new RuntimeException("Unit synthesisEngine already set."); + } + this.synthesisEngine = synthesisEngine; + } + + public UnitGenerator getTopUnit() { + UnitGenerator unit = this; + // Climb to top of circuit hierarchy. + while (unit.circuit != null) { + unit = unit.circuit; + } + LOGGER.debug("getTopUnit " + this + " => " + unit); + return unit; + } + + protected void autoStop() { + synthesisEngine.autoStopUnit(getTopUnit()); + } + + /** Calculate signal based on halflife of an exponential decay. */ + public double convertHalfLifeToMultiplier(double halfLife) { + if (halfLife < (2.0 * getFramePeriod())) { + return 1.0; + } else { + // Oddly enough, this code is valid for both PeakFollower and AsymptoticRamp. + return 1.0 - Math.pow(0.5, 1.0 / (halfLife * getSynthesisEngine().getFrameRate())); + } + } + + protected double incrementWrapPhase(double currentPhase, double phaseIncrement) { + currentPhase += phaseIncrement; + + if (currentPhase >= 1.0) { + currentPhase -= 2.0; + } else if (currentPhase < -1.0) { + currentPhase += 2.0; + } + return currentPhase; + } + + /** Calculate rate based on phase going from 0.0 to 1.0 in time. */ + protected double convertTimeToRate(double time) { + double period2X = synthesisEngine.getInverseNyquist(); + if (time < period2X) { + return 1.0; + } else { + return getFramePeriod() / time; + } + } + + /** Flatten output ports so we don't output a changing signal when stopped. */ + public void flattenOutputs() { + for (UnitPort port : ports.values()) { + if (port instanceof UnitOutputPort) { + ((UnitOutputPort) port).flatten(); + } + } + } + + public void setCircuit(Circuit circuit) { + if ((this.circuit != null) && (circuit != null)) { + throw new RuntimeException("Unit is already in a circuit."); + } + // LOGGER.info( "setCircuit in unit " + this + " with circuit " + circuit ); + this.circuit = circuit; + } + + public Circuit getCircuit() { + return circuit; + } + + public void pullData(long frameCount, int start, int limit) { + // Don't generate twice in case the paths merge. + if (enabled && (frameCount > lastFrameCount)) { + // Do this first to block recursion when there is a feedback loop. + lastFrameCount = frameCount; + // Then pull from all the units that are upstream. + for (UnitPort port : ports.values()) { + if (port instanceof ConnectableInput) { + ((ConnectableInput) port).pullData(frameCount, start, limit); + } + } + // Finally generate using outputs of the upstream units. + generate(start, limit); + } + } + + public boolean isEnabled() { + return enabled; + } + + /** + * If enabled, then a unit will execute if its output is connected to another unit that is + * executed. If not enabled then it will not execute and will not pull data from units that are + * connected to its inputs. Disabling a unit at the output of a tree of units can be used to + * turn off the entire tree, thus saving CPU cycles. + * + * @param enabled + * @see UnitGate#setupAutoDisable(UnitGenerator) + * @see #start() + */ + public void setEnabled(boolean enabled) { + this.enabled = enabled; + if (!enabled) { + flattenOutputs(); + } + } + + /** + * Some units, for example LineOut and FixedRateMonoWriter, will only work if + * started explicitly. Other units will run when downstream units are started. + * + * @return true if you should call start() for this unit + */ + public boolean isStartRequired() { + return false; + } + + /** + * Start executing this unit directly by adding it to a "run list" of units in the synthesis + * engine. This method is normally only called for the final unit in a chain, for example a + * LineOut. When that final unit executes it will "pull" data from any units connected to its + * inputs. Those units will then pull data their inputs until the entire chain is executed. If + * units are connected in a circle then this will be detected and the infinite recursion will be + * blocked. + * + * @see #setEnabled(boolean) + */ + public void start() { + if (getSynthesisEngine() == null) { + throw new RuntimeException("This " + this.getClass().getName() + + " was not add()ed to a Synthesizer."); + } + getSynthesisEngine().startUnit(this); + } + + /** + * Start a unit at the specified time. + * + * @param time + * @see #start() + */ + public void start(double time) { + start(new TimeStamp(time)); + } + + /** + * Start a unit at the specified time. + * + * @param timeStamp + * @see #start() + */ + public void start(TimeStamp timeStamp) { + if (getSynthesisEngine() == null) { + throw new RuntimeException("This " + this.getClass().getName() + + " was not add()ed to a Synthesizer."); + } + getSynthesisEngine().startUnit(this, timeStamp); + } + + /** + * Stop a unit at the specified time. + * + * @param time + * @see #start() + */ + public void stop(double time) { + stop(new TimeStamp(time)); + } + + public void stop() { + getSynthesisEngine().stopUnit(this); + } + + public void stop(TimeStamp timeStamp) { + getSynthesisEngine().stopUnit(this, timeStamp); + } + + /** + * @deprecated ignored, frameRate comes from the SynthesisEngine + * @param rate + */ + @Deprecated + public void setFrameRate(int rate) { + } + + /** Needed by UnitSink */ + public UnitGenerator getUnitGenerator() { + return this; + } + + /** Needed by UnitVoice */ + public void setPort(String portName, double value, TimeStamp timeStamp) { + UnitInputPort port = (UnitInputPort) getPortByName(portName); + // LOGGER.debug("setPort " + port ); + if (port == null) { + LOGGER.warn("port was null for name " + portName + ", " + this.getClass().getName()); + } else { + port.set(value, timeStamp); + } + } + + public void printConnections() { + printConnections(System.out); + } + + public void printConnections(PrintStream out) { + printConnections(out, 0); + } + + public void printConnections(PrintStream out, int level) { + for (UnitPort port : getPorts()) { + if (port instanceof UnitInputPort) { + ((UnitInputPort) port).printConnections(out, level); + } + } + } + +} diff --git a/src/main/java/com/jsyn/unitgen/UnitOscillator.java b/src/main/java/com/jsyn/unitgen/UnitOscillator.java new file mode 100644 index 0000000..5d4c6fa --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/UnitOscillator.java @@ -0,0 +1,93 @@ +/* + * 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.unitgen; + +import com.jsyn.ports.UnitInputPort; +import com.jsyn.ports.UnitOutputPort; +import com.jsyn.ports.UnitVariablePort; +import com.softsynth.shared.time.TimeStamp; + +/** + * Base class for all oscillators. + * + * @author Phil Burk (C) 2009 Mobileer Inc + */ +public abstract class UnitOscillator extends UnitGenerator implements UnitVoice { + /** Frequency in Hertz. */ + public UnitInputPort frequency; + public UnitInputPort amplitude; + public UnitVariablePort phase; + public UnitOutputPort output; + + public static final double DEFAULT_FREQUENCY = 440.0; + public static final double DEFAULT_AMPLITUDE = 1.0; + + /* Define Unit Ports used by connect() and set(). */ + public UnitOscillator() { + addPort(frequency = new UnitInputPort(PORT_NAME_FREQUENCY)); + frequency.setup(40.0, DEFAULT_FREQUENCY, 8000.0); + addPort(amplitude = new UnitInputPort(PORT_NAME_AMPLITUDE, DEFAULT_AMPLITUDE)); + addPort(phase = new UnitVariablePort(PORT_NAME_PHASE)); + addPort(output = new UnitOutputPort(PORT_NAME_OUTPUT)); + } + + /** + * Convert a frequency in Hertz to a phaseIncrement in the range -1.0 to +1.0 + */ + public double convertFrequencyToPhaseIncrement(double freq) { + double phaseIncrement; + try { + phaseIncrement = freq * synthesisEngine.getInverseNyquist(); + } catch (NullPointerException e) { + throw new NullPointerException( + "Null Synth! You probably forgot to add this unit to the Synthesizer!"); + } + // Clip to range. + phaseIncrement = (phaseIncrement > 1.0) ? 1.0 : ((phaseIncrement < -1.0) ? -1.0 + : phaseIncrement); + return phaseIncrement; + } + + @Override + public UnitOutputPort getOutput() { + return output; + } + + public void noteOn(double freq, double ampl) { + frequency.set(freq); + amplitude.set(ampl); + } + + public void noteOff() { + amplitude.set(0.0); + } + + @Override + public void noteOff(TimeStamp timeStamp) { + amplitude.set(0.0, timeStamp); + } + + @Override + public void noteOn(double freq, double ampl, TimeStamp timeStamp) { + frequency.set(freq, timeStamp); + amplitude.set(ampl, timeStamp); + } + + @Override + public void usePreset(int presetIndex) { + } +} diff --git a/src/main/java/com/jsyn/unitgen/UnitSink.java b/src/main/java/com/jsyn/unitgen/UnitSink.java new file mode 100644 index 0000000..3e0f55e --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/UnitSink.java @@ -0,0 +1,43 @@ +/* + * 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.unitgen; + +import com.jsyn.ports.UnitInputPort; +import com.softsynth.shared.time.TimeStamp; + +/** + * Interface for unit generators that have an input. + * + * @author Phil Burk, (C) 2009 Mobileer Inc + */ +public interface UnitSink { + public UnitInputPort getInput(); + + /** + * Begin execution of this unit by the Synthesizer. The input will pull data from any output + * port that is connected from it. + */ + public void start(); + + public void start(TimeStamp timeStamp); + + public void stop(); + + public void stop(TimeStamp timeStamp); + + public UnitGenerator getUnitGenerator(); +} diff --git a/src/main/java/com/jsyn/unitgen/UnitSource.java b/src/main/java/com/jsyn/unitgen/UnitSource.java new file mode 100644 index 0000000..5ee8134 --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/UnitSource.java @@ -0,0 +1,30 @@ +/* + * 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.unitgen; + +import com.jsyn.ports.UnitOutputPort; + +/** + * Interface for things that have that have an output and an associated UnitGenerator. + * + * @author Phil Burk, (C) 2009 Mobileer Inc + */ +public interface UnitSource { + public UnitOutputPort getOutput(); + + public UnitGenerator getUnitGenerator(); +} diff --git a/src/main/java/com/jsyn/unitgen/UnitStreamWriter.java b/src/main/java/com/jsyn/unitgen/UnitStreamWriter.java new file mode 100644 index 0000000..0c5bd8b --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/UnitStreamWriter.java @@ -0,0 +1,53 @@ +/* + * 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.unitgen; + +import com.jsyn.io.AudioOutputStream; +import com.jsyn.ports.UnitInputPort; + +/** + * Base class for writing to an AudioOutputStream. + * + * Note that you must call start() on subclasses of this unit because it does not have an output for pulling data. + * + * @author Phil Burk (C) 2009 Mobileer Inc + */ +public abstract class UnitStreamWriter extends UnitGenerator implements UnitSink { + protected AudioOutputStream outputStream; + public UnitInputPort input; + + public AudioOutputStream getOutputStream() { + return outputStream; + } + + public void setOutputStream(AudioOutputStream outputStream) { + this.outputStream = outputStream; + } + + /** + * This unit won't do anything unless you start() it. + */ + @Override + public boolean isStartRequired() { + return true; + } + + @Override + public UnitInputPort getInput() { + return input; + } +} diff --git a/src/main/java/com/jsyn/unitgen/UnitVoice.java b/src/main/java/com/jsyn/unitgen/UnitVoice.java new file mode 100644 index 0000000..3f5e6ef --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/UnitVoice.java @@ -0,0 +1,59 @@ +/* + * Copyright 2011 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.unitgen; + +import com.jsyn.util.Instrument; +import com.jsyn.util.VoiceDescription; +import com.softsynth.shared.time.TimeStamp; + +/** + * A voice that can be allocated and played by the VoiceAllocator. + * + * @author Phil Burk (C) 2011 Mobileer Inc + * @see VoiceDescription + * @see Instrument + */ +public interface UnitVoice extends UnitSource { + /** + * Play whatever you consider to be a note on this voice. Do not be constrained by traditional + * definitions of notes or music. + * + * @param frequency in Hz related to the perceived pitch of the note. + * @param amplitude generally between 0.0 and 1.0 + * @param timeStamp when to play the note + */ + void noteOn(double frequency, double amplitude, TimeStamp timeStamp); + + void noteOff(TimeStamp timeStamp); + + /** + * Typically a UnitVoice will be a subclass of UnitGenerator, which just returns "this". + */ + @Override + public UnitGenerator getUnitGenerator(); + + /** + * Looks up a port using its name and sets the value. + * + * @param portName + * @param value + * @param timeStamp + */ + void setPort(String portName, double value, TimeStamp timeStamp); + + void usePreset(int presetIndex); +} diff --git a/src/main/java/com/jsyn/unitgen/Unzipper.java b/src/main/java/com/jsyn/unitgen/Unzipper.java new file mode 100644 index 0000000..c776ffb --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/Unzipper.java @@ -0,0 +1,47 @@ +/* + * Copyright 2014 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.unitgen; + +/** + * Used inside UnitGenerators for fast smoothing of inputs. + * + * @author Phil Burk (C) 2014 Mobileer Inc + */ +public class Unzipper { + private double target; + private double delta; + private double current; + private int counter; + // About 30 msec. Power of 2 so divide should be faster. + private static final int NUM_STEPS = 1024; + + public double smooth(double input) { + if (input != target) { + target = input; + delta = (target - current) / NUM_STEPS; + counter = NUM_STEPS; + } + if (counter > 0) { + if (--counter == 0) { + current = target; + } else { + current += delta; + } + } + return current; + } +} diff --git a/src/main/java/com/jsyn/unitgen/VariableRateDataReader.java b/src/main/java/com/jsyn/unitgen/VariableRateDataReader.java new file mode 100644 index 0000000..2ef163c --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/VariableRateDataReader.java @@ -0,0 +1,29 @@ +/* + * 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.unitgen; + +import com.jsyn.ports.UnitInputPort; + +public abstract class VariableRateDataReader extends SequentialDataReader { + /** A scaler for playback rate. Nominally 1.0. */ + public UnitInputPort rate; + + public VariableRateDataReader() { + super(); + addPort(rate = new UnitInputPort("Rate", 1.0)); + } +} diff --git a/src/main/java/com/jsyn/unitgen/VariableRateMonoReader.java b/src/main/java/com/jsyn/unitgen/VariableRateMonoReader.java new file mode 100644 index 0000000..52b7f1e --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/VariableRateMonoReader.java @@ -0,0 +1,115 @@ +/* + * 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.unitgen; + +import com.jsyn.data.FloatSample; +import com.jsyn.data.SegmentedEnvelope; +import com.jsyn.data.ShortSample; +import com.jsyn.ports.UnitOutputPort; + +/** + * This reader can play any SequentialData and will interpolate between adjacent values. It can play + * both {@link SegmentedEnvelope envelopes} and {@link FloatSample samples}. + * + *

+	// Queue an envelope to the dataQueue port.
+	ampEnv.dataQueue.queue(ampEnvelope);
+
+ * + * @author Phil Burk (C) 2009 Mobileer Inc + * @see FloatSample + * @see ShortSample + * @see SegmentedEnvelope + */ +public class VariableRateMonoReader extends VariableRateDataReader { + private double phase; // ranges from 0.0 to 1.0 + private double baseIncrement; + private double source; + private double current; + private double target; + private boolean starved; + private boolean ranout; + + public VariableRateMonoReader() { + super(); + addPort(output = new UnitOutputPort("Output")); + starved = true; + baseIncrement = 1.0; + } + + @Override + public void generate(int start, int limit) { + double[] amplitudes = amplitude.getValues(); + double[] rates = rate.getValues(); + double[] outputs = output.getValues(); + + for (int i = start; i < limit; i++) { + // Decrement phase and advance through queued data until phase back + // in range. + if (phase >= 1.0) { + while (phase >= 1.0) { + source = target; + phase -= 1.0; + baseIncrement = advanceToNextFrame(); + } + } else if ((i == 0) && (starved || !dataQueue.isTargetValid())) { + // A starved condition can only be cured at the beginning of a + // block. + source = target = current; + phase = 0.0; + baseIncrement = advanceToNextFrame(); + } + + // Interpolate along line segment. + current = ((target - source) * phase) + source; + outputs[i] = current * amplitudes[i]; + + double phaseIncrement = baseIncrement * rates[i]; + phase += limitPhaseIncrement(phaseIncrement); + } + + if (ranout) { + ranout = false; + if (dataQueue.testAndClearAutoStop()) { + autoStop(); + } + } + } + + public double limitPhaseIncrement(double phaseIncrement) { + return phaseIncrement; + } + + private double advanceToNextFrame() { + // Fire callbacks before getting next value because we already got the + // target value previously. + dataQueue.firePendingCallbacks(); + if (dataQueue.hasMore()) { + starved = false; + target = dataQueue.readNextMonoDouble(getFramePeriod()); + + // calculate phase increment; + return getFramePeriod() * dataQueue.getNormalizedRate(); + } else { + starved = true; + ranout = true; + phase = 0.0; + return 0.0; + } + } + +} diff --git a/src/main/java/com/jsyn/unitgen/VariableRateStereoReader.java b/src/main/java/com/jsyn/unitgen/VariableRateStereoReader.java new file mode 100644 index 0000000..0f9fce8 --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/VariableRateStereoReader.java @@ -0,0 +1,113 @@ +/* + * 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.unitgen; + +import com.jsyn.ports.UnitOutputPort; + +/** + * This reader can play any SequentialData and will interpolate between adjacent values. It can play + * both envelopes and samples. + * + * @author Phil Burk (C) 2009 Mobileer Inc + */ +public class VariableRateStereoReader extends VariableRateDataReader { + private double phase; + private double baseIncrement; + private double source0; + private double current0; + private double target0; + private double source1; + private double current1; + private double target1; + private boolean starved; + private boolean ranout; + + public VariableRateStereoReader() { + dataQueue.setNumChannels(2); + addPort(output = new UnitOutputPort(2, "Output")); + starved = true; + baseIncrement = 1.0; + } + + @Override + public void generate(int start, int limit) { + double[] amplitudes = amplitude.getValues(); + double[] rates = rate.getValues(); + double[] output0s = output.getValues(0); + double[] output1s = output.getValues(1); + + for (int i = start; i < limit; i++) { + // Decrement phase and advance through queued data until phase back + // in range. + if (phase >= 1.0) { + while (phase >= 1.0) { + source0 = target0; + source1 = target1; + phase -= 1.0; + baseIncrement = advanceToNextFrame(); + } + } else if ((i == 0) && (starved || !dataQueue.isTargetValid())) { + // A starved condition can only be cured at the beginning of a block. + source0 = target0 = current0; + source1 = target1 = current1; + phase = 0.0; + baseIncrement = advanceToNextFrame(); + } + + // Interpolate along line segment. + current0 = ((target0 - source0) * phase) + source0; + output0s[i] = current0 * amplitudes[i]; + current1 = ((target1 - source1) * phase) + source1; + output1s[i] = current1 * amplitudes[i]; + + double phaseIncrement = baseIncrement * rates[i]; + phase += limitPhaseIncrement(phaseIncrement); + } + + if (ranout) { + ranout = false; + if (dataQueue.testAndClearAutoStop()) { + autoStop(); + } + } + } + + public double limitPhaseIncrement(double phaseIncrement) { + return phaseIncrement; + } + + private double advanceToNextFrame() { + dataQueue.firePendingCallbacks(); + if (dataQueue.hasMore()) { + starved = false; + + dataQueue.beginFrame(getFramePeriod()); + target0 = dataQueue.readCurrentChannelDouble(0); + target1 = dataQueue.readCurrentChannelDouble(1); + dataQueue.endFrame(); + + // calculate phase increment; + return synthesisEngine.getFramePeriod() * dataQueue.getNormalizedRate(); + } else { + starved = true; + ranout = true; + phase = 0.0; + return 0.0; + } + } + +} diff --git a/src/main/java/com/jsyn/unitgen/WhiteNoise.java b/src/main/java/com/jsyn/unitgen/WhiteNoise.java new file mode 100644 index 0000000..b708e92 --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/WhiteNoise.java @@ -0,0 +1,56 @@ +/* + * Copyright 1997 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.unitgen; + +import com.jsyn.ports.UnitInputPort; +import com.jsyn.ports.UnitOutputPort; +import com.jsyn.util.PseudoRandom; + +/** + * WhiteNoise unit. This unit uses a pseudo-random number generator to produce white noise. The + * energy in a white noise signal is distributed evenly across the spectrum. A new random number is + * generated every frame. + * + * @author (C) 1997-2011 Phil Burk, Mobileer Inc + * @see RedNoise + */ +public class WhiteNoise extends UnitGenerator implements UnitSource { + private PseudoRandom randomNum; + public UnitInputPort amplitude; + public UnitOutputPort output; + + public WhiteNoise() { + randomNum = new PseudoRandom(); + addPort(amplitude = new UnitInputPort("Amplitude", UnitOscillator.DEFAULT_AMPLITUDE)); + addPort(output = new UnitOutputPort("Output")); + } + + @Override + public void generate(int start, int limit) { + double[] amplitudes = amplitude.getValues(); + double[] outputs = output.getValues(); + + for (int i = start; i < limit; i++) { + outputs[i] = randomNum.nextRandomDouble() * amplitudes[i]; + } + } + + @Override + public UnitOutputPort getOutput() { + return output; + } +} diff --git a/src/main/java/com/jsyn/unitgen/ZeroCrossingCounter.java b/src/main/java/com/jsyn/unitgen/ZeroCrossingCounter.java new file mode 100644 index 0000000..6cd36ea --- /dev/null +++ b/src/main/java/com/jsyn/unitgen/ZeroCrossingCounter.java @@ -0,0 +1,61 @@ +/* + * Copyright 1997 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.unitgen; + +import com.jsyn.ports.UnitInputPort; +import com.jsyn.ports.UnitOutputPort; + +/** + * Count zero crossings. Handy for unit tests. + * + * @author (C) 1997-2011 Phil Burk, Mobileer Inc + */ +public class ZeroCrossingCounter extends UnitGenerator { + private static final double THRESHOLD = 0.0001; + public UnitInputPort input; + public UnitOutputPort output; + + private long count; + private boolean armed; + + /* Define Unit Ports used by connect() and set(). */ + public ZeroCrossingCounter() { + addPort(input = new UnitInputPort("Input")); + addPort(output = new UnitOutputPort("Output")); + } + + @Override + public void generate(int start, int limit) { + double[] inputs = input.getValues(); + double[] outputs = output.getValues(); + + for (int i = start; i < limit; i++) { + double value = inputs[i]; + if (value < -THRESHOLD) { + armed = true; + } else if (armed & (value > THRESHOLD)) { + ++count; + armed = false; + } + outputs[i] = value; + } + } + + public long getCount() { + return count; + } +} diff --git a/src/main/java/com/jsyn/util/AudioSampleLoader.java b/src/main/java/com/jsyn/util/AudioSampleLoader.java new file mode 100644 index 0000000..b665933 --- /dev/null +++ b/src/main/java/com/jsyn/util/AudioSampleLoader.java @@ -0,0 +1,42 @@ +/* + * 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.util; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; + +import com.jsyn.data.FloatSample; + +public interface AudioSampleLoader { + /** + * Load a FloatSample from a File object. + */ + public FloatSample loadFloatSample(File fileIn) throws IOException; + + /** + * Load a FloatSample from an InputStream. This is handy when loading Resources from a JAR file. + */ + public FloatSample loadFloatSample(InputStream inputStream) throws IOException; + + /** + * Load a FloatSample from a URL.. This is handy when loading Resources from a website. + */ + public FloatSample loadFloatSample(URL url) throws IOException; + +} diff --git a/src/main/java/com/jsyn/util/AudioStreamReader.java b/src/main/java/com/jsyn/util/AudioStreamReader.java new file mode 100644 index 0000000..5a725c3 --- /dev/null +++ b/src/main/java/com/jsyn/util/AudioStreamReader.java @@ -0,0 +1,85 @@ +/* + * Copyright 2010 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.util; + +import com.jsyn.Synthesizer; +import com.jsyn.io.AudioFifo; +import com.jsyn.io.AudioInputStream; +import com.jsyn.ports.UnitInputPort; +import com.jsyn.unitgen.MonoStreamWriter; +import com.jsyn.unitgen.StereoStreamWriter; +import com.jsyn.unitgen.UnitStreamWriter; + +/** + * Reads audio signals from the background engine to a foreground application through an AudioFifo. + * Connect to the input port returned by getInput(). + * + * @author Phil Burk (C) 2010 Mobileer Inc + */ +public class AudioStreamReader implements AudioInputStream { + private UnitStreamWriter streamWriter; + private AudioFifo fifo; + + public AudioStreamReader(Synthesizer synth, int samplesPerFrame) { + if (samplesPerFrame == 1) { + streamWriter = new MonoStreamWriter(); + } else if (samplesPerFrame == 2) { + streamWriter = new StereoStreamWriter(); + } else { + throw new IllegalArgumentException("Only 1 or 2 samplesPerFrame supported."); + } + synth.add(streamWriter); + + fifo = new AudioFifo(); + fifo.setWriteWaitEnabled(!synth.isRealTime()); + fifo.setReadWaitEnabled(true); + fifo.allocate(32 * 1024); + streamWriter.setOutputStream(fifo); + streamWriter.start(); + } + + public UnitInputPort getInput() { + return streamWriter.input; + } + + /** How many values are available to read without blocking? */ + @Override + public int available() { + return fifo.available(); + } + + @Override + public void close() { + fifo.close(); + } + + @Override + public double read() { + return fifo.read(); + } + + @Override + public int read(double[] buffer) { + return fifo.read(buffer); + } + + @Override + public int read(double[] buffer, int start, int count) { + return fifo.read(buffer, start, count); + } + +} diff --git a/src/main/java/com/jsyn/util/AutoCorrelator.java b/src/main/java/com/jsyn/util/AutoCorrelator.java new file mode 100644 index 0000000..944d515 --- /dev/null +++ b/src/main/java/com/jsyn/util/AutoCorrelator.java @@ -0,0 +1,290 @@ +/* + * Copyright 2004 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.util; + +/** + * Calculate period of a repeated waveform in an array. This algorithm is based on a normalized + * auto-correlation function as dewscribed in: "A Smarter Way to Find Pitch" by Philip McLeod and + * Geoff Wyvill + * + * @author (C) 2004 Mobileer, PROPRIETARY and CONFIDENTIAL + */ +public class AutoCorrelator implements SignalCorrelator { + // A higher number will reject suboctaves more. + private static final float SUB_OCTAVE_REJECTION_FACTOR = 0.0005f; + // We can focus our analysis on the maxima + private static final int STATE_SEEKING_NEGATIVE = 0; + private static final int STATE_SEEKING_POSITIVE = 1; + private static final int STATE_SEEKING_MAXIMUM = 2; + private static final int[] tauAdvanceByState = { + 4, 2, 1 + }; + private int state; + + private float[] buffer; + // double buffer the diffs so we can view them + private float[] diffs; + private float[] diffs1; + private float[] diffs2; + private int cursor = -1; + private int tau; + + private float sumProducts; + private float sumSquares; + private float localMaximum; + private int localPosition; + private float bestMaximum; + private int bestPosition; + private int peakCounter; + // This factor was found empirically to reduce a systematic offset in the pitch. + private float pitchCorrectionFactor = 0.99988f; + + // Results of analysis. + private double period; + private double confidence; + private int minPeriod = 2; + private boolean bufferValid; + private double previousSample = 0.0; + private int maxWindowSize; + private float noiseThreshold = 0.001f; + + public AutoCorrelator(int numFrames) { + buffer = new float[numFrames]; + maxWindowSize = buffer.length / 2; + diffs1 = new float[2 + numFrames / 2]; + diffs2 = new float[diffs1.length]; + diffs = diffs1; + period = minPeriod; + reset(); + } + + // Scan assuming we will not wrap around the buffer. + private void rawDeltaScan(int last1, int last2, int count, int stride) { + for (int k = 0; k < count; k += stride) { + float d1 = buffer[last1 - k]; + float d2 = buffer[last2 - k]; + sumProducts += d1 * d2; + sumSquares += ((d1 * d1) + (d2 * d2)); + } + } + + // Do correlation when we know the splitLast will wrap around. + private void splitDeltaScan(int last1, int splitLast, int count, int stride) { + rawDeltaScan(last1, splitLast, splitLast, stride); + rawDeltaScan(last1 - splitLast, buffer.length - 1, count - splitLast, stride); + } + + private void checkDeltaScan(int last1, int last2, int count, int stride) { + if (count > last2) { + // Use recursion with reverse indexes to handle a double split. + checkDeltaScan(last2, last1, last2, stride); + checkDeltaScan(buffer.length - 1, last1 - last2, count - last2, stride); + } else if (count > last1) { + splitDeltaScan(last2, last1, count, stride); + } else { + rawDeltaScan(last1, last2, count, stride); + } + } + + // Perform correlation. Handle circular buffer wrap around. + // Normalized square difference function between -1.0 and +1.0. + private float topScan(int last1, int tau, int count, int stride) { + final float minimumResult = 0.00000001f; + + int last2 = last1 - tau; + if (last2 < 0) { + last2 += buffer.length; + } + sumProducts = 0.0f; + sumSquares = 0.0f; + checkDeltaScan(last1, last2, count, stride); + // Prevent divide by zero. + if (sumSquares < minimumResult) { + return minimumResult; + } + float correction = (float) Math.pow(pitchCorrectionFactor, tau); + + return (float) (2.0 * sumProducts / sumSquares) * correction; + } + + // Prepare for a new calculation. + private void reset() { + switchDiffs(); + int i = 0; + for (; i < minPeriod; i++) { + diffs[i] = 1.0f; + } + for (; i < diffs.length; i++) { + diffs[i] = 0.0f; + } + tau = minPeriod; + state = STATE_SEEKING_NEGATIVE; + peakCounter = 0; + bestMaximum = -1.0f; + bestPosition = -1; + } + + // Analyze new diff result. Incremental peak detection. + private void nextPeakAnalysis(int index) { + // Scale low frequency correlation down to reduce suboctave matching. + // Note that this has a side effect of reducing confidence value for low frequency sounds. + float value = diffs[index] * (1.0f - (index * SUB_OCTAVE_REJECTION_FACTOR)); + switch (state) { + case STATE_SEEKING_NEGATIVE: + if (value < -0.01f) { + state = STATE_SEEKING_POSITIVE; + } + break; + case STATE_SEEKING_POSITIVE: + if (value > 0.2f) { + state = STATE_SEEKING_MAXIMUM; + localMaximum = value; + localPosition = index; + } + break; + case STATE_SEEKING_MAXIMUM: + if (value > localMaximum) { + localMaximum = value; + localPosition = index; + } else if (value < -0.1f) { + peakCounter += 1; + if (localMaximum > bestMaximum) { + bestMaximum = localMaximum; + bestPosition = localPosition; + } + state = STATE_SEEKING_POSITIVE; + } + break; + } + } + + /** + * Generate interpolated maximum from index of absolute maximum using three point analysis. + */ + private double findPreciseMaximum(int indexMax) { + if (indexMax < 3) { + return 3.0; + } + if (indexMax == (diffs.length - 1)) { + return indexMax; + } + // Get 3 adjacent values. + double d1 = diffs[indexMax - 1]; + double d2 = diffs[indexMax]; + double d3 = diffs[indexMax + 1]; + + return interpolatePeak(d1, d2, d3) + indexMax; + } + + // Use quadratic fit to return offset between -0.5 and +0.5 from center. + protected static double interpolatePeak(double d1, double d2, double d3) { + return 0.5 * (d1 - d3) / (d1 - (2.0 * d2) + d3); + } + + // Calculate a little more for each sample. + // This spreads the CPU load out more evenly. + private boolean incrementalAnalysis() { + boolean updated = false; + if (bufferValid) { + // int windowSize = maxWindowSize; + // Interpolate between tau and maxWindowsSize based on confidence. + // If confidence is low then use bigger window. + int windowSize = (int) ((tau * confidence) + (maxWindowSize * (1.0 - confidence))); + + int stride = 1; + // int stride = (windowSize / 32) + 1; + + diffs[tau] = topScan(cursor, tau, windowSize, stride); + + // Check to see if the signal is strong enough to analyze. + // Look at sumPeriods on first correlation. + if ((tau == minPeriod) && (sumProducts < noiseThreshold)) { + // Update if we are dropping to zero confidence. + boolean result = (confidence > 0.0); + confidence = 0.0; + return result; + } + + nextPeakAnalysis(tau); + + // Reuse calculated values if we are not near a peak. + tau += 1; + int advance = tauAdvanceByState[state] - 1; + while ((advance > 0) && (tau < diffs.length)) { + diffs[tau] = diffs[tau - 1]; + tau++; + advance--; + } + + if ((peakCounter >= 4) || (tau >= maxWindowSize)) { + if (bestMaximum > 0.0) { + period = findPreciseMaximum(bestPosition); + // clip into range 0.0 to 1.0, low values are really bogus + confidence = (bestMaximum < 0.0) ? 0.0 : bestMaximum; + } else { + confidence = 0.0; + } + updated = true; + reset(); + } + } + return updated; + } + + @Override + public float[] getDiffs() { + // Return diffs that are not currently being used + return (diffs == diffs1) ? diffs2 : diffs1; + } + + private void switchDiffs() { + diffs = (diffs == diffs1) ? diffs2 : diffs1; + } + + @Override + public boolean addSample(double value) { + double average = (value + previousSample) * 0.5; + previousSample = value; + + cursor += 1; + if (cursor == buffer.length) { + cursor = 0; + bufferValid = true; + } + buffer[cursor] = (float) average; + + return incrementalAnalysis(); + } + + @Override + public double getPeriod() { + return period; + } + + @Override + public double getConfidence() { + return confidence; + } + + public float getPitchCorrectionFactor() { + return pitchCorrectionFactor; + } + + public void setPitchCorrectionFactor(float pitchCorrectionFactor) { + this.pitchCorrectionFactor = pitchCorrectionFactor; + } +} diff --git a/src/main/java/com/jsyn/util/Instrument.java b/src/main/java/com/jsyn/util/Instrument.java new file mode 100644 index 0000000..8a53304 --- /dev/null +++ b/src/main/java/com/jsyn/util/Instrument.java @@ -0,0 +1,38 @@ +/* + * Copyright 2011 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.util; + +import com.softsynth.shared.time.TimeStamp; + +/** + * A note player that references one or more voices by a noteNumber. This is similar to the MIDI + * protocol that references voices by an integer pitch or keyIndex. + * + * @author Phil Burk (C) 2011 Mobileer Inc + */ +public interface Instrument { + // This will be applied to the voice when noteOn is called. + void usePreset(int presetIndex, TimeStamp timeStamp); + + public void noteOn(int tag, double frequency, double amplitude, TimeStamp timeStamp); + + public void noteOff(int tag, TimeStamp timeStamp); + + public void setPort(int tag, String portName, double value, TimeStamp timeStamp); + + public void allNotesOff(TimeStamp timeStamp); +} diff --git a/src/main/java/com/jsyn/util/InstrumentLibrary.java b/src/main/java/com/jsyn/util/InstrumentLibrary.java new file mode 100644 index 0000000..65113dc --- /dev/null +++ b/src/main/java/com/jsyn/util/InstrumentLibrary.java @@ -0,0 +1,32 @@ +/* + * Copyright 2011 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.util; + +import com.jsyn.swing.InstrumentBrowser; + +/** + * A library of instruments that can be used to play notes. + * + * @author Phil Burk (C) 2011 Mobileer Inc + * @see InstrumentBrowser + */ + +public interface InstrumentLibrary { + public String getName(); + + public VoiceDescription[] getVoiceDescriptions(); +} diff --git a/src/main/java/com/jsyn/util/JavaSoundSampleLoader.java b/src/main/java/com/jsyn/util/JavaSoundSampleLoader.java new file mode 100644 index 0000000..56a654e --- /dev/null +++ b/src/main/java/com/jsyn/util/JavaSoundSampleLoader.java @@ -0,0 +1,149 @@ +/* + * Copyright 2011 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.util; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; + +import javax.sound.sampled.AudioFormat; +import javax.sound.sampled.AudioInputStream; +import javax.sound.sampled.AudioSystem; +import javax.sound.sampled.UnsupportedAudioFileException; + +import com.jsyn.data.FloatSample; + +/** + * Internal class for loading audio samples. Use SampleLoader instead. + * + * @author Phil Burk (C) 2011 Mobileer Inc + */ +class JavaSoundSampleLoader implements AudioSampleLoader { + /** + * Load a FloatSample from a File object. + */ + @Override + public FloatSample loadFloatSample(File fileIn) throws IOException { + try { + return loadFloatSample(AudioSystem.getAudioInputStream(fileIn)); + } catch (UnsupportedAudioFileException e) { + throw new IOException(e); + } + } + + /** + * Load a FloatSample from an InputStream. This is handy when loading Resources from a JAR file. + */ + @Override + public FloatSample loadFloatSample(InputStream inputStream) throws IOException { + try { + return loadFloatSample(AudioSystem.getAudioInputStream(inputStream)); + } catch (UnsupportedAudioFileException e) { + throw new IOException(e); + } + } + + /** + * Load a FloatSample from a URL.. This is handy when loading Resources from a website. + */ + @Override + public FloatSample loadFloatSample(URL url) throws IOException { + try { + return loadFloatSample(AudioSystem.getAudioInputStream(url)); + } catch (UnsupportedAudioFileException e) { + throw new IOException(e); + } + } + + private FloatSample loadFloatSample(javax.sound.sampled.AudioInputStream audioInputStream) + throws IOException, UnsupportedAudioFileException { + float[] floatData = null; + FloatSample sample = null; + int bytesPerFrame = audioInputStream.getFormat().getFrameSize(); + if (bytesPerFrame == AudioSystem.NOT_SPECIFIED) { + // some audio formats may have unspecified frame size + // in that case we may read any amount of bytes + bytesPerFrame = 1; + } + AudioFormat format = audioInputStream.getFormat(); + if (format.getEncoding() == AudioFormat.Encoding.PCM_SIGNED) { + floatData = loadSignedPCM(audioInputStream); + } + sample = new FloatSample(floatData, format.getChannels()); + sample.setFrameRate(format.getFrameRate()); + return sample; + } + + private float[] loadSignedPCM(AudioInputStream audioInputStream) throws IOException, + UnsupportedAudioFileException { + int totalSamplesRead = 0; + AudioFormat format = audioInputStream.getFormat(); + int numFrames = (int) audioInputStream.getFrameLength(); + int numSamples = format.getChannels() * numFrames; + float[] data = new float[numSamples]; + final int bytesPerFrame = format.getFrameSize(); + // Set an arbitrary buffer size of 1024 frames. + int numBytes = 1024 * bytesPerFrame; + byte[] audioBytes = new byte[numBytes]; + int numBytesRead = 0; + int numFramesRead = 0; + // Try to read numBytes bytes from the file. + while ((numBytesRead = audioInputStream.read(audioBytes)) != -1) { + int bytesRemainder = numBytesRead % bytesPerFrame; + if (bytesRemainder != 0) { + // TODO Read until you get enough data. + throw new IOException("Read partial block of sample data!"); + } + + if (audioInputStream.getFormat().getSampleSizeInBits() == 16) { + if (format.isBigEndian()) { + SampleLoader.decodeBigI16ToF32(audioBytes, 0, numBytesRead, data, + totalSamplesRead); + } else { + SampleLoader.decodeLittleI16ToF32(audioBytes, 0, numBytesRead, data, + totalSamplesRead); + } + } else if (audioInputStream.getFormat().getSampleSizeInBits() == 24) { + if (format.isBigEndian()) { + SampleLoader.decodeBigI24ToF32(audioBytes, 0, numBytesRead, data, + totalSamplesRead); + } else { + SampleLoader.decodeLittleI24ToF32(audioBytes, 0, numBytesRead, data, + totalSamplesRead); + } + } else if (audioInputStream.getFormat().getSampleSizeInBits() == 32) { + if (format.isBigEndian()) { + SampleLoader.decodeBigI32ToF32(audioBytes, 0, numBytesRead, data, + totalSamplesRead); + } else { + SampleLoader.decodeLittleI32ToF32(audioBytes, 0, numBytesRead, data, + totalSamplesRead); + } + } else { + throw new UnsupportedAudioFileException( + "Only 16, 24 or 32 bit PCM samples supported."); + } + + // Calculate the number of frames actually read. + numFramesRead = numBytesRead / bytesPerFrame; + totalSamplesRead += numFramesRead * format.getChannels(); + } + return data; + } + +} diff --git a/src/main/java/com/jsyn/util/JavaTools.java b/src/main/java/com/jsyn/util/JavaTools.java new file mode 100644 index 0000000..570e4c4 --- /dev/null +++ b/src/main/java/com/jsyn/util/JavaTools.java @@ -0,0 +1,64 @@ +/* + * 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.util; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class JavaTools { + + private static final Logger LOGGER = LoggerFactory.getLogger(JavaTools.class); + + @SuppressWarnings("rawtypes") + public static Class loadClass(String className, boolean verbose) { + Class newClass = null; + try { + newClass = Class.forName(className); + } catch (Throwable e) { + if (verbose) + LOGGER.debug("Caught " + e); + } + if (newClass == null) { + try { + ClassLoader systemLoader = ClassLoader.getSystemClassLoader(); + newClass = Class.forName(className, true, systemLoader); + } catch (Throwable e) { + if (verbose) + LOGGER.debug("Caught " + e); + } + } + return newClass; + } + + /** + * First try Class.forName(). If this fails, try Class.forName() using + * ClassLoader.getSystemClassLoader(). + * + * @return Class or null + */ + @SuppressWarnings("rawtypes") + public static Class loadClass(String className) { + /** + * First try Class.forName(). If this fails, try Class.forName() using + * ClassLoader.getSystemClassLoader(). + * + * @return Class or null + */ + return loadClass(className, true); + } + +} diff --git a/src/main/java/com/jsyn/util/MultiChannelSynthesizer.java b/src/main/java/com/jsyn/util/MultiChannelSynthesizer.java new file mode 100644 index 0000000..da7f6c7 --- /dev/null +++ b/src/main/java/com/jsyn/util/MultiChannelSynthesizer.java @@ -0,0 +1,404 @@ +/* + * Copyright 2016 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.util; + +import com.jsyn.Synthesizer; +import com.jsyn.engine.SynthesisEngine; +import com.jsyn.midi.MidiConstants; +import com.jsyn.ports.UnitInputPort; +import com.jsyn.ports.UnitOutputPort; +import com.jsyn.unitgen.ExponentialRamp; +import com.jsyn.unitgen.LinearRamp; +import com.jsyn.unitgen.Multiply; +import com.jsyn.unitgen.Pan; +import com.jsyn.unitgen.PowerOfTwo; +import com.jsyn.unitgen.SineOscillator; +import com.jsyn.unitgen.TwoInDualOut; +import com.jsyn.unitgen.UnitGenerator; +import com.jsyn.unitgen.UnitOscillator; +import com.jsyn.unitgen.UnitVoice; +import com.softsynth.math.AudioMath; +import com.softsynth.shared.time.TimeStamp; + +/** + * General purpose synthesizer with "channels" + * that could be used to implement a MIDI synthesizer. + * + * Each channel has: + *

+ * lfo -> pitchToLinear -> [VOICES] -> volume* -> panner
+ * bend --/
+ * 
+ * + * Note: this class is experimental and subject to change. + * + * @author Phil Burk (C) 2016 Mobileer Inc + */ +public class MultiChannelSynthesizer { + private Synthesizer synth; + private TwoInDualOut outputUnit; + private ChannelContext[] channels; + private final static int MAX_VELOCITY = 127; + private double mMasterAmplitude = 0.25; + + private class ChannelGroupContext { + private VoiceDescription voiceDescription; + private UnitVoice[] voices; + private VoiceAllocator allocator; + + ChannelGroupContext(int numVoices, VoiceDescription voiceDescription) { + this.voiceDescription = voiceDescription; + + voices = new UnitVoice[numVoices]; + for (int i = 0; i < numVoices; i++) { + UnitVoice voice = voiceDescription.createUnitVoice(); + UnitGenerator ugen = voice.getUnitGenerator(); + synth.add(ugen); + voices[i] = voice; + + } + allocator = new VoiceAllocator(voices); + } + } + + private class ChannelContext { + private UnitOscillator lfo; + private PowerOfTwo pitchToLinear; + private LinearRamp timbreRamp; + private LinearRamp pressureRamp; + private ExponentialRamp volumeRamp; + private Multiply volumeMultiplier; + private Pan panner; + private double vibratoRate = 5.0; + private double bendRangeOctaves = 2.0 / 12.0; + private int presetIndex; + private ChannelGroupContext groupContext; + VoiceOperation voiceOperation = new VoiceOperation() { + @Override + public void operate (UnitVoice voice) { + voice.usePreset(presetIndex); + connectVoice(voice); + } + }; + + void setup(ChannelGroupContext groupContext) { + this.groupContext = groupContext; + synth.add(pitchToLinear = new PowerOfTwo()); + synth.add(lfo = new SineOscillator()); // TODO use a MorphingOscillator or switch + // between S&H etc. + // Use a ramp to smooth out the timbre changes. + // This helps reduce pops from changing filter cutoff too abruptly. + synth.add(timbreRamp = new LinearRamp()); + timbreRamp.time.set(0.02); + synth.add(pressureRamp = new LinearRamp()); + pressureRamp.time.set(0.02); + synth.add(volumeRamp = new ExponentialRamp()); + volumeRamp.input.set(1.0); + volumeRamp.time.set(0.02); + synth.add(volumeMultiplier = new Multiply()); + synth.add(panner = new Pan()); + + pitchToLinear.input.setValueAdded(true); // so we can sum pitch bend + lfo.output.connect(pitchToLinear.input); + lfo.amplitude.set(0.0); + lfo.frequency.set(vibratoRate); + + volumeRamp.output.connect(volumeMultiplier.inputB); + volumeMultiplier.output.connect(panner.input); + panner.output.connect(0, outputUnit.inputA, 0); // Use MultiPassthrough + panner.output.connect(1, outputUnit.inputB, 0); + } + + private void connectVoice(UnitVoice voice) { + UnitGenerator ugen = voice.getUnitGenerator(); + // Hook up some channel controllers to standard ports on the voice. + UnitInputPort freqMod = (UnitInputPort) ugen + .getPortByName(UnitGenerator.PORT_NAME_FREQUENCY_SCALER); + if (freqMod != null) { + freqMod.disconnectAll(); + pitchToLinear.output.connect(freqMod); + } + UnitInputPort timbrePort = (UnitInputPort) ugen + .getPortByName(UnitGenerator.PORT_NAME_TIMBRE); + if (timbrePort != null) { + timbrePort.disconnectAll(); + timbreRamp.output.connect(timbrePort); + timbreRamp.input.setup(timbrePort); + } + UnitInputPort pressurePort = (UnitInputPort) ugen + .getPortByName(UnitGenerator.PORT_NAME_PRESSURE); + if (pressurePort != null) { + pressurePort.disconnectAll(); + pressureRamp.output.connect(pressurePort); + pressureRamp.input.setup(pressurePort); + } + voice.getOutput().disconnectAll(); + voice.getOutput().connect(volumeMultiplier.inputA); // mono mix all the voices + } + + void programChange(int program) { + int programWrapped = program % groupContext.voiceDescription.getPresetCount(); + String name = groupContext.voiceDescription.getPresetNames()[programWrapped]; + //LOGGER.debug("Preset[" + program + "] = " + name); + presetIndex = programWrapped; + } + + void noteOff(int noteNumber, double amplitude) { + groupContext.allocator.noteOff(noteNumber, synth.createTimeStamp()); + } + + void noteOff(int noteNumber, double amplitude, TimeStamp timeStamp) { + groupContext.allocator.noteOff(noteNumber, timeStamp); + } + + void noteOn(int noteNumber, double amplitude) { + noteOn(noteNumber, amplitude, synth.createTimeStamp()); + } + + void noteOn(int noteNumber, double amplitude, TimeStamp timeStamp) { + double frequency = AudioMath.pitchToFrequency(noteNumber); + //LOGGER.debug("noteOn(noteNumber) -> " + frequency + " Hz"); + groupContext.allocator.noteOn(noteNumber, frequency, amplitude, voiceOperation, timeStamp); + } + + public void setPitchBend(double offset) { + pitchToLinear.input.set(bendRangeOctaves * offset); + } + + public void setBendRange(double semitones) { + bendRangeOctaves = semitones / 12.0; + } + + public void setVibratoDepth(double semitones) { + lfo.amplitude.set(semitones); + } + + public void setVolume(double volume) { + double min = SynthesisEngine.DB96; + double max = 1.0; + double ratio = max / min; + double value = min * Math.pow(ratio, volume); + volumeRamp.input.set(value); + } + + public void setPan(double pan) { + panner.pan.set(pan); + } + + /* + * @param timbre normalized 0 to 1 + */ + public void setTimbre(double timbre) { + double min = timbreRamp.input.getMinimum(); + double max = timbreRamp.input.getMaximum(); + double value = min + (timbre * (max - min)); + timbreRamp.input.set(value); + } + + /* + * @param pressure normalized 0 to 1 + */ + public void setPressure(double pressure) { + double min = pressureRamp.input.getMinimum(); + double max = pressureRamp.input.getMaximum(); + double ratio = max / min; + double value = min * Math.pow(ratio, pressure); + pressureRamp.input.set(value); + } + } + + /** + * Construct a synthesizer with a maximum of 16 channels like MIDI. + */ + public MultiChannelSynthesizer() { + this(MidiConstants.MAX_CHANNELS); + } + + + public MultiChannelSynthesizer(int maxChannels) { + channels = new ChannelContext[maxChannels]; + for (int i = 0; i < channels.length; i++) { + channels[i] = new ChannelContext(); + } + } + + /** + * Specify a VoiceDescription to use with multiple channels. + * + * @param synth + * @param startChannel channel index is zero based + * @param numChannels + * @param voicesPerChannel + * @param voiceDescription + */ + public void setup(Synthesizer synth, int startChannel, int numChannels, int voicesPerChannel, + VoiceDescription voiceDescription) { + this.synth = synth; + if (outputUnit == null) { + synth.add(outputUnit = new TwoInDualOut()); + } + ChannelGroupContext groupContext = new ChannelGroupContext(voicesPerChannel, + voiceDescription); + for (int i = 0; i < numChannels; i++) { + channels[startChannel + i].setup(groupContext); + } + } + + public void programChange(int channel, int program) { + ChannelContext channelContext = channels[channel]; + channelContext.programChange(program); + } + + + /** + * Turn off a note. + * @param channel + * @param noteNumber + * @param velocity between 0 and 127, will be scaled by masterAmplitude + */ + public void noteOff(int channel, int noteNumber, int velocity) { + double amplitude = velocity * (1.0 / MAX_VELOCITY); + noteOff(channel, noteNumber, amplitude); + } + + /** + * Turn off a note. + * @param channel + * @param noteNumber + * @param amplitude between 0 and 1.0, will be scaled by masterAmplitude + */ + public void noteOff(int channel, int noteNumber, double amplitude) { + ChannelContext channelContext = channels[channel]; + channelContext.noteOff(noteNumber, amplitude * mMasterAmplitude); + } + + /** + * Turn off a note. + * @param channel + * @param noteNumber + * @param amplitude between 0 and 1.0, will be scaled by masterAmplitude + */ + public void noteOff(int channel, int noteNumber, double amplitude, TimeStamp timeStamp) { + ChannelContext channelContext = channels[channel]; + channelContext.noteOff(noteNumber, amplitude * mMasterAmplitude, timeStamp); + } + + /** + * Turn on a note. + * @param channel + * @param noteNumber + * @param velocity between 0 and 127, will be scaled by masterAmplitude + */ + public void noteOn(int channel, int noteNumber, int velocity) { + double amplitude = velocity * (1.0 / MAX_VELOCITY); + noteOn(channel, noteNumber, amplitude); + } + + /** + * Turn on a note. + * @param channel + * @param noteNumber + * @param amplitude between 0 and 1.0, will be scaled by masterAmplitude + */ + public void noteOn(int channel, int noteNumber, double amplitude, TimeStamp timeStamp) { + ChannelContext channelContext = channels[channel]; + channelContext.noteOn(noteNumber, amplitude * mMasterAmplitude, timeStamp); + } + + /** + * Turn on a note. + * @param channel + * @param noteNumber + * @param amplitude between 0 and 1.0, will be scaled by masterAmplitude + */ + public void noteOn(int channel, int noteNumber, double amplitude) { + ChannelContext channelContext = channels[channel]; + channelContext.noteOn(noteNumber, amplitude * mMasterAmplitude); + } + + /** + * Set a pitch offset that will be scaled by the range for the channel. + * + * @param channel + * @param offset ranges from -1.0 to +1.0 + */ + public void setPitchBend(int channel, double offset) { + //LOGGER.debug("setPitchBend[" + channel + "] = " + offset); + ChannelContext channelContext = channels[channel]; + channelContext.setPitchBend(offset); + } + + public void setBendRange(int channel, double semitones) { + ChannelContext channelContext = channels[channel]; + channelContext.setBendRange(semitones); + } + + public void setPressure(int channel, double pressure) { + ChannelContext channelContext = channels[channel]; + channelContext.setPressure(pressure); + } + + public void setVibratoDepth(int channel, double semitones) { + ChannelContext channelContext = channels[channel]; + channelContext.setVibratoDepth(semitones); + } + + public void setTimbre(int channel, double timbre) { + ChannelContext channelContext = channels[channel]; + channelContext.setTimbre(timbre); + } + + /** + * Set volume for entire channel. + * + * @param channel + * @param volume normalized between 0.0 and 1.0 + */ + public void setVolume(int channel, double volume) { + ChannelContext channelContext = channels[channel]; + channelContext.setVolume(volume); + } + + /** + * Pan from left to right. + * + * @param channel + * @param pan ranges from -1.0 to +1.0 + */ + public void setPan(int channel, double pan) { + ChannelContext channelContext = channels[channel]; + channelContext.setPan(pan); + } + + /** + * @return stereo output port + */ + public UnitOutputPort getOutput() { + return outputUnit.output; + } + + /** + * Set amplitude for a single voice when the velocity is 127. + * @param masterAmplitude + */ + public void setMasterAmplitude(double masterAmplitude) { + mMasterAmplitude = masterAmplitude; + } + public double getMasterAmplitude() { + return mMasterAmplitude; + } +} diff --git a/src/main/java/com/jsyn/util/NumericOutput.java b/src/main/java/com/jsyn/util/NumericOutput.java new file mode 100644 index 0000000..e30975f --- /dev/null +++ b/src/main/java/com/jsyn/util/NumericOutput.java @@ -0,0 +1,193 @@ +/* + * Copyright 1999 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.util; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Formatted numeric output. Convert integers and floats to strings based on field widths and + * desired decimal places. + * + * @author Phil Burk (C) 1999 SoftSynth.com + */ + +public class NumericOutput { + + private static final Logger LOGGER = LoggerFactory.getLogger(NumericOutput.class); + + static char digitToChar(int digit) { + if (digit > 9) { + return (char) ('A' + digit - 10); + } else { + return (char) ('0' + digit); + } + } + + public static String integerToString(int n, int width, boolean leadingZeros) { + return integerToString(n, width, leadingZeros, 10); + } + + public static String integerToString(int n, int width) { + return integerToString(n, width, false, 10); + } + + public static String integerToString(int n, int width, boolean leadingZeros, int radix) { + if (width > 32) + width = 32; + StringBuffer buf = new StringBuffer(); + long ln = n; + boolean ifNeg = false; + // only do sign if decimal + if (radix != 10) { + // LOGGER.debug("MASK before : ln = " + ln ); + ln = ln & 0x00000000FFFFFFFFL; + // LOGGER.debug("MASK after : ln = " + ln ); + } else if (ln < 0) { + ifNeg = true; + ln = -ln; + } + if (ln == 0) { + buf.append('0'); + } else { + // LOGGER.debug(" ln = " + ln ); + while (ln > 0) { + int rem = (int) (ln % radix); + buf.append(digitToChar(rem)); + ln = ln / radix; + } + } + if (leadingZeros) { + int pl = width; + if (ifNeg) + pl -= 1; + for (int i = buf.length(); i < pl; i++) + buf.append('0'); + } + if (ifNeg) + buf.append('-'); + // leading spaces + for (int i = buf.length(); i < width; i++) + buf.append(' '); + // reverse buffer to put characters in correct order + buf.reverse(); + + return buf.toString(); + } + + /** + * Convert double to string. + * + * @param width = minimum width of formatted string + * @param places = number of digits displayed after decimal point + */ + public static String doubleToString(double value, int width, int places) { + return doubleToString(value, width, places, false); + } + + /** + * Convert double to string. + * + * @param width = minimum width of formatted string + * @param places = number of digits displayed after decimal point + */ + public static String doubleToString(double value, int width, int places, boolean leadingZeros) { + if (width > 32) + width = 32; + if (places > 16) + places = 16; + + boolean ifNeg = false; + if (value < 0.0) { + ifNeg = true; + value = -value; + } + // round at relevant decimal place + value += 0.5 * Math.pow(10.0, 0 - places); + int ival = (int) Math.floor(value); + // get portion after decimal point as an integer + int fval = (int) ((value - Math.floor(value)) * Math.pow(10.0, places)); + String result = ""; + + result += integerToString(ival, 0, false, 10); + result += "."; + result += integerToString(fval, places, true, 10); + + if (leadingZeros) { + // prepend leading zeros and {-} + int zw = width; + if (ifNeg) + zw -= 1; + while (result.length() < zw) + result = "0" + result; + if (ifNeg) + result = "-" + result; + } else { + // prepend {-} and leading spaces + if (ifNeg) + result = "-" + result; + while (result.length() < width) + result = " " + result; + } + return result; + } + + static void testInteger(int n) { + LOGGER.debug("Test " + n + ", 0x" + Integer.toHexString(n) + ", %" + + Integer.toBinaryString(n)); + LOGGER.debug(" +,8,t,10 = " + integerToString(n, 8, true, 10)); + LOGGER.debug(" +,8,f,10 = " + integerToString(n, 8, false, 10)); + LOGGER.debug(" -,8,t,10 = " + integerToString(-n, 8, true, 10)); + LOGGER.debug(" -,8,f,10 = " + integerToString(-n, 8, false, 10)); + LOGGER.debug(" +,8,t,16 = " + integerToString(n, 8, true, 16)); + LOGGER.debug(" +,8,f,16 = " + integerToString(n, 8, false, 16)); + LOGGER.debug(" -,8,t,16 = " + integerToString(-n, 8, true, 16)); + LOGGER.debug(" -,8,f,16 = " + integerToString(-n, 8, false, 16)); + LOGGER.debug(" +,8,t, 2 = " + integerToString(n, 8, true, 2)); + LOGGER.debug(" +,8,f, 2 = " + integerToString(n, 8, false, 2)); + } + + static void testDouble(double value) { + LOGGER.debug("Test " + value); + LOGGER.debug(" +,5,1 = " + doubleToString(value, 5, 1)); + LOGGER.debug(" -,5,1 = " + doubleToString(-value, 5, 1)); + + LOGGER.debug(" +,14,3 = " + doubleToString(value, 14, 3)); + LOGGER.debug(" -,14,3 = " + doubleToString(-value, 14, 3)); + + LOGGER.debug(" +,6,2,true = " + doubleToString(value, 6, 2, true)); + LOGGER.debug(" -,6,2,true = " + doubleToString(-value, 6, 2, true)); + } + + public static void main(String argv[]) { + LOGGER.debug("Test NumericOutput"); + testInteger(0); + testInteger(1); + testInteger(16); + testInteger(23456); + testInteger(0x23456); + testInteger(0x89ABC); + testDouble(0.0); + testDouble(0.0678); + testDouble(0.1234567); + testDouble(1.234567); + testDouble(12.34567); + testDouble(123.4567); + testDouble(1234.5678); + + } +} diff --git a/src/main/java/com/jsyn/util/PolyphonicInstrument.java b/src/main/java/com/jsyn/util/PolyphonicInstrument.java new file mode 100644 index 0000000..08460d0 --- /dev/null +++ b/src/main/java/com/jsyn/util/PolyphonicInstrument.java @@ -0,0 +1,155 @@ +/* + * Copyright 2011 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.util; + +import com.jsyn.ports.UnitInputPort; +import com.jsyn.ports.UnitOutputPort; +import com.jsyn.ports.UnitPort; +import com.jsyn.unitgen.Circuit; +import com.jsyn.unitgen.Multiply; +import com.jsyn.unitgen.PassThrough; +import com.jsyn.unitgen.UnitGenerator; +import com.jsyn.unitgen.UnitSource; +import com.jsyn.unitgen.UnitVoice; +import com.softsynth.shared.time.TimeStamp; + +/** + * The API for this class is likely to change. Please comment on its usefulness. + * + * @author Phil Burk (C) 2011 Mobileer Inc + */ + +public class PolyphonicInstrument extends Circuit implements UnitSource, Instrument { + private Multiply mixer; + private UnitVoice[] voices; + private VoiceAllocator voiceAllocator; + public UnitInputPort amplitude; + + public PolyphonicInstrument(UnitVoice[] voices) { + this.voices = voices; + voiceAllocator = new VoiceAllocator(voices); + add(mixer = new Multiply()); + // Mix all the voices to one output. + for (UnitVoice voice : voices) { + UnitGenerator unit = voice.getUnitGenerator(); + boolean wasEnabled = unit.isEnabled(); + // This overrides the enabled property of the voice. + add(unit); + voice.getOutput().connect(mixer.inputA); + // restore + unit.setEnabled(wasEnabled); + } + + addPort(amplitude = mixer.inputB, "Amplitude"); + amplitude.setup(0.0001, 0.4, 2.0); + exportAllInputPorts(); + } + + /** + * Connect a PassThrough unit to the input ports of the voices so that they can be controlled + * together using a single port. Note that this will prevent their individual use. So the + * "Frequency" and "Amplitude" ports are excluded. Note that this method is a bit funky and is + * likely to change. + */ + public void exportAllInputPorts() { + // Iterate through the ports. + for (UnitPort port : voices[0].getUnitGenerator().getPorts()) { + if (port instanceof UnitInputPort) { + UnitInputPort inputPort = (UnitInputPort) port; + String voicePortName = inputPort.getName(); + // FIXME Need better way to identify ports that are per note. + if (!voicePortName.equals("Frequency") && !voicePortName.equals("Amplitude")) { + exportNamedInputPort(voicePortName); + } + } + } + } + + /** + * Create a UnitInputPort for the circuit that is connected to the named port on each voice + * through a PassThrough unit. This allows you to control all of the voices at once. + * + * @param portName + * @see exportAllInputPorts + */ + public void exportNamedInputPort(String portName) { + UnitInputPort voicePort = null; + PassThrough fanout = new PassThrough(); + for (UnitVoice voice : voices) { + voicePort = (UnitInputPort) voice.getUnitGenerator().getPortByName(portName); + fanout.output.connect(voicePort); + } + if (voicePort != null) { + addPort(fanout.input, portName); + fanout.input.setup(voicePort); + } + } + + @Override + public UnitOutputPort getOutput() { + return mixer.output; + } + + @Override + public void usePreset(int presetIndex) { + usePreset(presetIndex, getSynthesisEngine().createTimeStamp()); + } + + // FIXME - no timestamp on UnitVoice + @Override + public void usePreset(int presetIndex, TimeStamp timeStamp) { + // Apply preset to all voices. + for (UnitVoice voice : voices) { + voice.usePreset(presetIndex); + } + // Then copy values from first voice to instrument. + for (UnitPort port : voices[0].getUnitGenerator().getPorts()) { + if (port instanceof UnitInputPort) { + UnitInputPort inputPort = (UnitInputPort) port; + // FIXME Need better way to identify ports that are per note. + UnitInputPort fanPort = (UnitInputPort) getPortByName(inputPort.getName()); + if ((fanPort != null) && (fanPort != amplitude)) { + fanPort.set(inputPort.get()); + } + } + } + } + + @Override + public void noteOn(int tag, double frequency, double amplitude, TimeStamp timeStamp) { + voiceAllocator.noteOn(tag, frequency, amplitude, timeStamp); + } + + @Override + public void noteOff(int tag, TimeStamp timeStamp) { + voiceAllocator.noteOff(tag, timeStamp); + } + + @Override + public void setPort(int tag, String portName, double value, TimeStamp timeStamp) { + voiceAllocator.setPort(tag, portName, value, timeStamp); + } + + @Override + public void allNotesOff(TimeStamp timeStamp) { + voiceAllocator.allNotesOff(timeStamp); + } + + public synchronized boolean isOn(int tag) { + return voiceAllocator.isOn(tag); + } +} diff --git a/src/main/java/com/jsyn/util/PseudoRandom.java b/src/main/java/com/jsyn/util/PseudoRandom.java new file mode 100644 index 0000000..e92b669 --- /dev/null +++ b/src/main/java/com/jsyn/util/PseudoRandom.java @@ -0,0 +1,89 @@ +/* + * 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. + */ +/** + * Sep 9, 2009 + * com.jsyn.engine.units.SynthRandom.java + */ + +package com.jsyn.util; + +import java.util.Random; + +/** + * Pseudo-random numbers using predictable and fast linear-congruential method. + * + * @author Phil Burk (C) 2009 Mobileer Inc Translated from 'C' to Java by Lisa + * Tolentino. + */ +public class PseudoRandom { + // We must shift 1L or else we get a negative number! + private static final double INT_TO_DOUBLE = (1.0 / (1L << 31)); + private long seed = 99887766; + + /** + * Create an instance of SynthRandom. + */ + public PseudoRandom() { + this(new Random().nextInt()); + } + + /** + * Create an instance of PseudoRandom. + */ + public PseudoRandom(int seed) { + setSeed(seed); + } + + public void setSeed(int seed) { + this.seed = (long) seed; + } + + public int getSeed() { + return (int) seed; + } + + /** + * Returns the next random double from 0.0 to 1.0 + * + * @return value from 0.0 to 1.0 + */ + public double random() { + int positiveInt = nextRandomInteger() & 0x7FFFFFFF; + return positiveInt * INT_TO_DOUBLE; + } + + /** + * Returns the next random double from -1.0 to 1.0 + * + * @return value from -1.0 to 1.0 + */ + public double nextRandomDouble() { + return nextRandomInteger() * INT_TO_DOUBLE; + } + + /** Calculate random 32 bit number using linear-congruential method. */ + public int nextRandomInteger() { + // Use values for 64-bit sequence from MMIX by Donald Knuth. + seed = (seed * 6364136223846793005L) + 1442695040888963407L; + return (int) (seed >> 32); // The higher bits have a longer sequence. + } + + public int choose(int range) { + long positiveInt = nextRandomInteger() & 0x7FFFFFFF; + long temp = positiveInt * range; + return (int) (temp >> 31); + } +} diff --git a/src/main/java/com/jsyn/util/RecursiveSequenceGenerator.java b/src/main/java/com/jsyn/util/RecursiveSequenceGenerator.java new file mode 100644 index 0000000..0d6e451 --- /dev/null +++ b/src/main/java/com/jsyn/util/RecursiveSequenceGenerator.java @@ -0,0 +1,214 @@ +/* + * Copyright 1997 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.util; + +import java.util.Random; + +/** + * Generate a sequence of integers based on a recursive mining of previous material. Notes are + * generated by one of the following formula: + * + *
+ * 
+ * value[n] = value[n-delay] + offset;
+ * 
+ * 
+ * + * The parameters delay and offset are randomly generated. This algorithm was first developed in + * 1977 for a class project in FORTRAN. It was ported to Forth for HMSL in the late 80's. It was + * then ported to Java for JSyn in 1997. + * + * @author Phil Burk (C) 1997,2011 Mobileer Inc + */ +public class RecursiveSequenceGenerator { + private int delay = 1; + private int maxValue; + private int maxInterval; + private double desiredDensity = 0.5; + + private int offset; + private int values[]; + private boolean enables[]; + private int cursor; + private int countdown = -1; + private double actualDensity; + private int beatsPerMeasure = 8; + private Random random; + + public RecursiveSequenceGenerator() { + this(25, 7, 64); + } + + public RecursiveSequenceGenerator(int maxValue, int maxInterval, int arraySize) { + values = new int[arraySize]; + enables = new boolean[arraySize]; + this.maxValue = maxValue; + this.maxInterval = maxInterval; + for (int i = 0; i < values.length; i++) { + values[i] = maxValue / 2; + enables[i] = isNextEnabled(false); + } + } + + /** Set density of notes. 0.0 to 1.0 */ + public void setDensity(double density) { + desiredDensity = density; + } + + public double getDensity() { + return desiredDensity; + } + + /** Set maximum for generated value. */ + public void setMaxValue(int maxValue) { + this.maxValue = maxValue; + } + + public int getMaxValue() { + return maxValue; + } + + /** Set maximum for generated value. */ + public void setMaxInterval(int maxInterval) { + this.maxInterval = maxInterval; + } + + public int getMaxInterval() { + return maxInterval; + } + + /* Determine whether next in sequence should occur. */ + public boolean isNextEnabled(boolean preferance) { + /* Calculate note density using low pass IIR filter. */ + double newDensity = (actualDensity * 0.9) + (preferance ? 0.1 : 0.0); + /* Invert enable to push density towards desired level, with hysteresis. */ + if (preferance && (newDensity > ((desiredDensity * 0.7) + 0.3))) + preferance = false; + else if (!preferance && (newDensity < (desiredDensity * 0.7))) + preferance = true; + actualDensity = (actualDensity * 0.9) + (preferance ? 0.1 : 0.0); + return preferance; + } + + public int randomPowerOf2(int maxExp) { + return (1 << (int) (random.nextDouble() * (maxExp + 1))); + } + + /** Random number evenly distributed from -maxInterval to +maxInterval */ + public int randomEvenInterval() { + return (int) (random.nextDouble() * ((maxInterval * 2) + 1)) - maxInterval; + } + + void calcNewOffset() { + offset = randomEvenInterval(); + } + + public void randomize() { + + delay = randomPowerOf2(4); + calcNewOffset(); + // LOGGER.debug("NewSeq: delay = " + delay + ", offset = " + + // offset ); + } + + /** Change parameters based on random countdown. */ + public int next() { + // If this sequence is finished, start a new one. + if (countdown-- < 0) { + randomize(); + countdown = randomPowerOf2(3); + } + return nextValue(); + } + + /** Change parameters using a probability based on beatIndex. */ + public int next(int beatIndex) { + int beatMod = beatIndex % beatsPerMeasure; + switch (beatMod) { + case 0: + if (Math.random() < 0.90) + randomize(); + break; + case 2: + case 6: + if (Math.random() < 0.15) + randomize(); + break; + case 4: + if (Math.random() < 0.30) + randomize(); + break; + default: + if (Math.random() < 0.07) + randomize(); + break; + } + return nextValue(); + } + + /** Generate nextValue based on current delay and offset */ + public int nextValue() { + // Generate index into circular value buffer. + int idx = (cursor - delay); + if (idx < 0) + idx += values.length; + + // Generate new value. Calculate new offset if too high or low. + int nextVal = 0; + int timeout = 100; + while (timeout > 0) { + nextVal = values[idx] + offset; + if ((nextVal >= 0) && (nextVal < maxValue)) + break; + // Prevent endless loops when maxValue changes. + if (nextVal > (maxValue + maxInterval - 1)) { + nextVal = maxValue; + break; + } + calcNewOffset(); + timeout--; + // LOGGER.debug("NextVal = " + nextVal + ", offset = " + + // offset ); + } + if (timeout <= 0) { + System.err.println("RecursiveSequence: nextValue timed out. offset = " + offset); + nextVal = maxValue / 2; + offset = 0; + } + + // Save new value in circular buffer. + values[cursor] = nextVal; + + boolean playIt = enables[cursor] = isNextEnabled(enables[idx]); + cursor++; + if (cursor >= values.length) + cursor = 0; + + // LOGGER.debug("nextVal = " + nextVal ); + + return playIt ? nextVal : -1; + } + + public Random getRandom() { + return random; + } + + public void setRandom(Random random) { + this.random = random; + } + +} diff --git a/src/main/java/com/jsyn/util/SampleLoader.java b/src/main/java/com/jsyn/util/SampleLoader.java new file mode 100644 index 0000000..170b4cb --- /dev/null +++ b/src/main/java/com/jsyn/util/SampleLoader.java @@ -0,0 +1,230 @@ +/* + * Copyright 2011 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.util; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; + +import com.jsyn.data.FloatSample; +import com.jsyn.util.soundfile.CustomSampleLoader; + +/** + * Load a FloatSample from various sources. The default loader uses custom code to load WAV or AIF + * files. Supported data formats are 16, 24 and 32 bit PCM, and 32-bit float. Compressed formats + * such as unsigned 8-bit, uLaw, A-Law and MP3 are not support. If you need to load one of those + * files try setJavaSoundPreferred(true). Or convert it to a supported format using Audacity or Sox + * or some other sample file tool. Here is an example of loading a sample from a file. + * + *
+ * 
+ *     File sampleFile = new File("guitar.wav");
+ *     FloatSample sample = SampleLoader.loadFloatSample( sampleFile );
+ * 
+ * 
+ * + * @author Phil Burk (C) 2011 Mobileer Inc + */ +public class SampleLoader { + private static boolean javaSoundPreferred = false; + private static final String JS_LOADER_NAME = "com.jsyn.util.JavaSoundSampleLoader"; + + /** + * Try to create an implementation of AudioSampleLoader. + * + * @return A device supported on this platform. + */ + private static AudioSampleLoader createLoader() { + AudioSampleLoader loader = null; + try { + if (javaSoundPreferred) { + loader = (AudioSampleLoader) JavaTools.loadClass(JS_LOADER_NAME).newInstance(); + } else { + loader = new CustomSampleLoader(); + } + } catch (InstantiationException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + return loader; + } + + /** + * Load a FloatSample from a File object. + */ + public static FloatSample loadFloatSample(File fileIn) throws IOException { + AudioSampleLoader loader = SampleLoader.createLoader(); + return loader.loadFloatSample(fileIn); + } + + /** + * Load a FloatSample from an InputStream. This is handy when loading Resources from a JAR file. + */ + public static FloatSample loadFloatSample(InputStream inputStream) throws IOException { + AudioSampleLoader loader = SampleLoader.createLoader(); + return loader.loadFloatSample(inputStream); + } + + /** + * Load a FloatSample from a URL.. This is handy when loading Resources from a website. + */ + public static FloatSample loadFloatSample(URL url) throws IOException { + AudioSampleLoader loader = SampleLoader.createLoader(); + return loader.loadFloatSample(url); + } + + public static boolean isJavaSoundPreferred() { + return javaSoundPreferred; + } + + /** + * If set true then the audio file parser from JavaSound will be used. Note that JavaSound + * cannot load audio files containing floating point data. But it may be able to load some + * compressed data formats such as uLaw. + * + * Note: JavaSound is not supported on Android. + * + * @param javaSoundPreferred + */ + public static void setJavaSoundPreferred(boolean javaSoundPreferred) { + SampleLoader.javaSoundPreferred = javaSoundPreferred; + } + + /** + * Decode 24 bit samples from a BigEndian byte array into a float array. The samples will be + * normalized into the range -1.0 to +1.0. + * + * @param audioBytes raw data from an audio file + * @param offset first element of byte array + * @param numBytes number of bytes to process + * @param data array to be filled with floats + * @param outputOffset first element of float array to be filled + */ + public static void decodeBigI24ToF32(byte[] audioBytes, int offset, int numBytes, float[] data, + int outputOffset) { + int lastByte = offset + numBytes; + int byteCursor = offset; + int floatCursor = outputOffset; + while (byteCursor < lastByte) { + int hi = ((audioBytes[byteCursor++]) & 0x00FF); + int mid = ((audioBytes[byteCursor++]) & 0x00FF); + int lo = ((audioBytes[byteCursor++]) & 0x00FF); + int value = (hi << 24) | (mid << 16) | (lo << 8); + data[floatCursor++] = value * (1.0f / Integer.MAX_VALUE); + } + } + + public static void decodeBigI16ToF32(byte[] audioBytes, int offset, int numBytes, float[] data, + int outputOffset) { + int lastByte = offset + numBytes; + int byteCursor = offset; + int floatCursor = outputOffset; + while (byteCursor < lastByte) { + int hi = ((audioBytes[byteCursor++]) & 0x00FF); + int lo = ((audioBytes[byteCursor++]) & 0x00FF); + short value = (short) ((hi << 8) | lo); + data[floatCursor++] = value * (1.0f / 32768); + } + } + + public static void decodeBigF32ToF32(byte[] audioBytes, int offset, int numBytes, float[] data, + int outputOffset) { + int lastByte = offset + numBytes; + int byteCursor = offset; + int floatCursor = outputOffset; + while (byteCursor < lastByte) { + int bits = audioBytes[byteCursor++]; + bits = (bits << 8) | ((audioBytes[byteCursor++]) & 0x00FF); + bits = (bits << 8) | ((audioBytes[byteCursor++]) & 0x00FF); + bits = (bits << 8) | ((audioBytes[byteCursor++]) & 0x00FF); + data[floatCursor++] = Float.intBitsToFloat(bits); + } + } + + public static void decodeBigI32ToF32(byte[] audioBytes, int offset, int numBytes, float[] data, + int outputOffset) { + int lastByte = offset + numBytes; + int byteCursor = offset; + int floatCursor = outputOffset; + while (byteCursor < lastByte) { + int value = audioBytes[byteCursor++]; // MSB + value = (value << 8) | ((audioBytes[byteCursor++]) & 0x00FF); + value = (value << 8) | ((audioBytes[byteCursor++]) & 0x00FF); + value = (value << 8) | ((audioBytes[byteCursor++]) & 0x00FF); + data[floatCursor++] = value * (1.0f / Integer.MAX_VALUE); + } + } + + public static void decodeLittleF32ToF32(byte[] audioBytes, int offset, int numBytes, + float[] data, int outputOffset) { + int lastByte = offset + numBytes; + int byteCursor = offset; + int floatCursor = outputOffset; + while (byteCursor < lastByte) { + int bits = ((audioBytes[byteCursor++]) & 0x00FF); // LSB + bits += ((audioBytes[byteCursor++]) & 0x00FF) << 8; + bits += ((audioBytes[byteCursor++]) & 0x00FF) << 16; + bits += (audioBytes[byteCursor++]) << 24; + data[floatCursor++] = Float.intBitsToFloat(bits); + } + } + + public static void decodeLittleI32ToF32(byte[] audioBytes, int offset, int numBytes, + float[] data, int outputOffset) { + int lastByte = offset + numBytes; + int byteCursor = offset; + int floatCursor = outputOffset; + while (byteCursor < lastByte) { + int value = ((audioBytes[byteCursor++]) & 0x00FF); + value += ((audioBytes[byteCursor++]) & 0x00FF) << 8; + value += ((audioBytes[byteCursor++]) & 0x00FF) << 16; + value += (audioBytes[byteCursor++]) << 24; + data[floatCursor++] = value * (1.0f / Integer.MAX_VALUE); + } + } + + public static void decodeLittleI24ToF32(byte[] audioBytes, int offset, int numBytes, + float[] data, int outputOffset) { + int lastByte = offset + numBytes; + int byteCursor = offset; + int floatCursor = outputOffset; + while (byteCursor < lastByte) { + int lo = ((audioBytes[byteCursor++]) & 0x00FF); + int mid = ((audioBytes[byteCursor++]) & 0x00FF); + int hi = ((audioBytes[byteCursor++]) & 0x00FF); + int value = (hi << 24) | (mid << 16) | (lo << 8); + data[floatCursor++] = value * (1.0f / Integer.MAX_VALUE); + } + } + + public static void decodeLittleI16ToF32(byte[] audioBytes, int offset, int numBytes, + float[] data, int outputOffset) { + int lastByte = offset + numBytes; + int byteCursor = offset; + int floatCursor = outputOffset; + while (byteCursor < lastByte) { + int lo = ((audioBytes[byteCursor++]) & 0x00FF); + int hi = ((audioBytes[byteCursor++]) & 0x00FF); + short value = (short) ((hi << 8) | lo); + float sample = value * (1.0f / 32768); + data[floatCursor++] = sample; + } + } + +} diff --git a/src/main/java/com/jsyn/util/SignalCorrelator.java b/src/main/java/com/jsyn/util/SignalCorrelator.java new file mode 100644 index 0000000..ebdd46b --- /dev/null +++ b/src/main/java/com/jsyn/util/SignalCorrelator.java @@ -0,0 +1,48 @@ +/* + * Copyright 2011 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.util; + +/** + * Interface used to evaluate various algorithms for pitch detection. + * + * @author Phil Burk (C) 2011 Mobileer Inc + */ +public interface SignalCorrelator { + /** + * Add a sample to be analyzed. The samples will generally be held in a circular buffer. + * + * @param value + * @return true if a new period value has been generated + */ + public boolean addSample(double value); + + /** + * @return the estimated period of the waveform in samples + */ + public double getPeriod(); + + /** + * Measure of how confident the analyzer is of the last result. + * + * @return quality of the estimate between 0.0 and 1.0 + */ + public double getConfidence(); + + /** For internal debugging. */ + public float[] getDiffs(); + +} diff --git a/src/main/java/com/jsyn/util/StreamingThread.java b/src/main/java/com/jsyn/util/StreamingThread.java new file mode 100644 index 0000000..682f476 --- /dev/null +++ b/src/main/java/com/jsyn/util/StreamingThread.java @@ -0,0 +1,121 @@ +/* + * Copyright 2011 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.util; + +import java.io.IOException; + +import com.jsyn.io.AudioInputStream; +import com.jsyn.io.AudioOutputStream; + +/** + * Read from an AudioInputStream and write to an AudioOutputStream as a background thread. + * + * @author Phil Burk (C) 2011 Mobileer Inc + */ +public class StreamingThread extends Thread { + private AudioInputStream inputStream; + private AudioOutputStream outputStream; + private int framesPerBuffer = 1024; + private volatile boolean go = true; + private TransportModel transportModel; + private long framePosition; + private long maxFrames; + private int samplesPerFrame = 1; + + public StreamingThread(AudioInputStream inputStream, AudioOutputStream outputStream) { + this.inputStream = inputStream; + this.outputStream = outputStream; + } + + @Override + public void run() { + double[] buffer = new double[framesPerBuffer * samplesPerFrame]; + try { + transportModel.firePositionChanged(framePosition); + transportModel.fireStateChanged(TransportModel.STATE_RUNNING); + int framesToRead = geteFramesToRead(buffer); + while (go && (framesToRead > 0)) { + int samplesToRead = framesToRead * samplesPerFrame; + while (samplesToRead > 0) { + int samplesRead = inputStream.read(buffer, 0, samplesToRead); + outputStream.write(buffer, 0, samplesRead); + samplesToRead -= samplesRead; + } + framePosition += framesToRead; + transportModel.firePositionChanged(framePosition); + framesToRead = geteFramesToRead(buffer); + } + transportModel.fireStateChanged(TransportModel.STATE_STOPPED); + } catch (IOException e) { + e.printStackTrace(); + } + } + + private int geteFramesToRead(double[] buffer) { + if (maxFrames > 0) { + long numToRead = maxFrames - framePosition; + if (numToRead < 0) { + return 0; + } else if (numToRead > framesPerBuffer) { + numToRead = framesPerBuffer; + } + return (int) numToRead; + } else { + return framesPerBuffer; + } + } + + public int getFramesPerBuffer() { + return framesPerBuffer; + } + + /** + * Only call this before the thread has started. + * + * @param framesPerBuffer + */ + public void setFramesPerBuffer(int framesPerBuffer) { + this.framesPerBuffer = framesPerBuffer; + } + + public void requestStop() { + go = false; + } + + public TransportModel getTransportModel() { + return transportModel; + } + + public void setTransportModel(TransportModel transportModel) { + this.transportModel = transportModel; + } + + /** + * @param maxFrames + */ + public void setMaxFrames(long maxFrames) { + this.maxFrames = maxFrames; + } + + public int getSamplesPerFrame() { + return samplesPerFrame; + } + + public void setSamplesPerFrame(int samplesPerFrame) { + this.samplesPerFrame = samplesPerFrame; + } +} diff --git a/src/main/java/com/jsyn/util/TransportListener.java b/src/main/java/com/jsyn/util/TransportListener.java new file mode 100644 index 0000000..3c8b048 --- /dev/null +++ b/src/main/java/com/jsyn/util/TransportListener.java @@ -0,0 +1,31 @@ +/* + * 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.util; + +public interface TransportListener { + /** + * @param transportModel + * @param framePosition position in frames + */ + void positionChanged(TransportModel transportModel, long framePosition); + + /** + * @param transportModel + * @param state for example TransportModel.STATE_STOPPED + */ + void stateChanged(TransportModel transportModel, int state); +} diff --git a/src/main/java/com/jsyn/util/TransportModel.java b/src/main/java/com/jsyn/util/TransportModel.java new file mode 100644 index 0000000..bcc75be --- /dev/null +++ b/src/main/java/com/jsyn/util/TransportModel.java @@ -0,0 +1,67 @@ +/* + * 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.util; + +import java.util.concurrent.CopyOnWriteArrayList; + +public class TransportModel { + public static final int STATE_STOPPED = 0; + public static final int STATE_PAUSED = 1; + public static final int STATE_RUNNING = 2; + + private CopyOnWriteArrayList listeners = new CopyOnWriteArrayList(); + private int state = STATE_STOPPED; + private long position; + + public void addTransportListener(TransportListener listener) { + listeners.add(listener); + } + + public void removeTransportListener(TransportListener listener) { + listeners.remove(listener); + } + + public void setState(int newState) { + state = newState; + fireStateChanged(newState); + } + + public int getState() { + return state; + } + + public void setPosition(long newPosition) { + position = newPosition; + firePositionChanged(newPosition); + } + + public long getPosition() { + return position; + } + + public void fireStateChanged(int newState) { + for (TransportListener listener : listeners) { + listener.stateChanged(this, newState); + } + } + + public void firePositionChanged(long newPosition) { + for (TransportListener listener : listeners) { + listener.positionChanged(this, newPosition); + } + } +} diff --git a/src/main/java/com/jsyn/util/VoiceAllocator.java b/src/main/java/com/jsyn/util/VoiceAllocator.java new file mode 100644 index 0000000..f20f7a5 --- /dev/null +++ b/src/main/java/com/jsyn/util/VoiceAllocator.java @@ -0,0 +1,258 @@ +/* + * Copyright 2011 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.util; + +import com.jsyn.Synthesizer; +import com.jsyn.unitgen.UnitVoice; +import com.softsynth.shared.time.ScheduledCommand; +import com.softsynth.shared.time.TimeStamp; + +/** + * Allocate voices based on an integer tag. The tag could, for example, be a MIDI note number. Or a + * tag could be an int that always increments. Use the same tag to refer to a voice for noteOn() and + * noteOff(). If no new voices are available then a voice in use will be stolen. + * + * @author Phil Burk (C) 2011 Mobileer Inc + */ +public class VoiceAllocator implements Instrument { + private int maxVoices; + private VoiceTracker[] trackers; + private long tick; + private Synthesizer synthesizer; + private static final int UNASSIGNED_PRESET = -1; + private int mPresetIndex = UNASSIGNED_PRESET; + + /** + * Create an allocator for the array of UnitVoices. The array must be full of instantiated + * UnitVoices that are connected to some kind of mixer. + * + * @param voices + */ + public VoiceAllocator(UnitVoice[] voices) { + maxVoices = voices.length; + trackers = new VoiceTracker[maxVoices]; + for (int i = 0; i < maxVoices; i++) { + trackers[i] = new VoiceTracker(); + trackers[i].voice = voices[i]; + } + } + + public Synthesizer getSynthesizer() { + if (synthesizer == null) { + synthesizer = trackers[0].voice.getUnitGenerator().getSynthesizer(); + } + return synthesizer; + } + + private class VoiceTracker { + UnitVoice voice; + int tag = -1; + int presetIndex = UNASSIGNED_PRESET; + long when; + boolean on; + + public void off() { + on = false; + when = tick++; + } + } + + /** + * @return number of UnitVoices passed to the allocator. + */ + public int getVoiceCount() { + return maxVoices; + } + + private VoiceTracker findVoice(int tag) { + for (VoiceTracker tracker : trackers) { + if (tracker.tag == tag) { + return tracker; + } + } + return null; + } + + private VoiceTracker stealVoice() { + VoiceTracker bestOff = null; + VoiceTracker bestOn = null; + for (VoiceTracker tracker : trackers) { + if (tracker.voice == null) { + return tracker; + } + // If we have a bestOff voice then don't even bother with on voices. + else if (bestOff != null) { + // Older off voice? + if (!tracker.on && (tracker.when < bestOff.when)) { + bestOff = tracker; + } + } else if (tracker.on) { + if (bestOn == null) { + bestOn = tracker; + } else if (tracker.when < bestOn.when) { + bestOn = tracker; + } + } else { + bestOff = tracker; + } + } + if (bestOff != null) { + return bestOff; + } else { + return bestOn; + } + } + + /** + * Allocate a Voice associated with this tag. It will first pick a voice already assigned to + * that tag. Next it will pick the oldest voice that is off. Next it will pick the oldest voice + * that is on. If you are using timestamps to play the voice in the future then you should use + * the noteOn() noteOff() and setPort() methods. + * + * @param tag + * @return Voice that is most available. + */ + protected synchronized UnitVoice allocate(int tag) { + VoiceTracker tracker = allocateTracker(tag); + return tracker.voice; + } + + private VoiceTracker allocateTracker(int tag) { + VoiceTracker tracker = findVoice(tag); + if (tracker == null) { + tracker = stealVoice(); + } + tracker.tag = tag; + tracker.when = tick++; + tracker.on = true; + return tracker; + } + + protected synchronized boolean isOn(int tag) { + VoiceTracker tracker = findVoice(tag); + if (tracker != null) { + return tracker.on; + } + return false; + } + + protected synchronized UnitVoice off(int tag) { + VoiceTracker tracker = findVoice(tag); + if (tracker != null) { + tracker.off(); + return tracker.voice; + } + return null; + } + + /** Turn off all the note currently on. */ + @Override + public void allNotesOff(TimeStamp timeStamp) { + getSynthesizer().scheduleCommand(timeStamp, new ScheduledCommand() { + @Override + public void run() { + for (VoiceTracker tracker : trackers) { + if (tracker.on) { + tracker.voice.noteOff(getSynthesizer().createTimeStamp()); + tracker.off(); + } + } + } + }); + } + + /** + * Play a note on the voice and associate it with the given tag. if needed a new voice will be + * allocated and an old voice may be turned off. + */ + @Override + public void noteOn(final int tag, final double frequency, final double amplitude, + TimeStamp timeStamp) { + getSynthesizer().scheduleCommand(timeStamp, new ScheduledCommand() { + @Override + public void run() { + VoiceTracker voiceTracker = allocateTracker(tag); + if (voiceTracker.presetIndex != mPresetIndex) { + voiceTracker.voice.usePreset(mPresetIndex); + voiceTracker.presetIndex = mPresetIndex; + } + voiceTracker.voice.noteOn(frequency, amplitude, getSynthesizer().createTimeStamp()); + } + }); + } + + /** + * Play a note on the voice and associate it with the given tag. if needed a new voice will be + * allocated and an old voice may be turned off. + * Apply an operation to the voice. + */ + public void noteOn(final int tag, + final double frequency, + final double amplitude, + final VoiceOperation operation, + TimeStamp timeStamp) { + getSynthesizer().scheduleCommand(timeStamp, new ScheduledCommand() { + @Override + public void run() { + VoiceTracker voiceTracker = allocateTracker(tag); + operation.operate(voiceTracker.voice); + voiceTracker.voice.noteOn(frequency, amplitude, getSynthesizer().createTimeStamp()); + } + }); + } + + /** Turn off the voice associated with the given tag if allocated. */ + @Override + public void noteOff(final int tag, TimeStamp timeStamp) { + getSynthesizer().scheduleCommand(timeStamp, new ScheduledCommand() { + @Override + public void run() { + VoiceTracker voiceTracker = findVoice(tag); + if (voiceTracker != null) { + voiceTracker.voice.noteOff(getSynthesizer().createTimeStamp()); + off(tag); + } + } + }); + } + + /** Set a port on the voice associated with the given tag if allocated. */ + @Override + public void setPort(final int tag, final String portName, final double value, + TimeStamp timeStamp) { + getSynthesizer().scheduleCommand(timeStamp, new ScheduledCommand() { + @Override + public void run() { + VoiceTracker voiceTracker = findVoice(tag); + if (voiceTracker != null) { + voiceTracker.voice.setPort(portName, value, getSynthesizer().createTimeStamp()); + } + } + }); + } + + @Override + public void usePreset(final int presetIndex, TimeStamp timeStamp) { + getSynthesizer().scheduleCommand(timeStamp, new ScheduledCommand() { + @Override + public void run() { + mPresetIndex = presetIndex; + } + }); + } + +} diff --git a/src/main/java/com/jsyn/util/VoiceDescription.java b/src/main/java/com/jsyn/util/VoiceDescription.java new file mode 100644 index 0000000..b7be044 --- /dev/null +++ b/src/main/java/com/jsyn/util/VoiceDescription.java @@ -0,0 +1,68 @@ +/* + * Copyright 2011 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.util; + +import com.jsyn.unitgen.UnitVoice; + +/** + * Describe a voice so that a user can pick it out of an InstrumentLibrary. + * + * @author Phil Burk (C) 2011 Mobileer Inc + * @see PolyphonicInstrument + */ +public abstract class VoiceDescription { + private String name; + private String[] presetNames; + + public VoiceDescription(String name, String[] presetNames) { + this.name = name; + this.presetNames = presetNames; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public int getPresetCount() { + return presetNames.length; + } + + public String[] getPresetNames() { + return presetNames; + } + + public abstract String[] getTags(int presetIndex); + + /** + * Instantiate one of these voices. You may want to call usePreset(n) on the voice after + * instantiating it. + * + * @return a voice + */ + public abstract UnitVoice createUnitVoice(); + + public abstract String getVoiceClassName(); + + @Override + public String toString() { + return name + "[" + getPresetCount() + "]"; + } +} diff --git a/src/main/java/com/jsyn/util/VoiceOperation.java b/src/main/java/com/jsyn/util/VoiceOperation.java new file mode 100644 index 0000000..cd3b48e --- /dev/null +++ b/src/main/java/com/jsyn/util/VoiceOperation.java @@ -0,0 +1,7 @@ +package com.jsyn.util; + +import com.jsyn.unitgen.UnitVoice; + +public interface VoiceOperation { + public void operate(UnitVoice voice); +} diff --git a/src/main/java/com/jsyn/util/WaveFileWriter.java b/src/main/java/com/jsyn/util/WaveFileWriter.java new file mode 100644 index 0000000..32e9995 --- /dev/null +++ b/src/main/java/com/jsyn/util/WaveFileWriter.java @@ -0,0 +1,293 @@ +/* + * Copyright 2011 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.util; + +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.RandomAccessFile; + +import com.jsyn.io.AudioOutputStream; + +/** + * Write audio data to a WAV file. + * + *
+ * 
+ * WaveFileWriter writer = new WaveFileWriter(file);
+ * writer.setFrameRate(22050);
+ * writer.setBitsPerSample(24);
+ * writer.write(floatArray);
+ * writer.close();
+ * 
+ * 
+ * + * @author Phil Burk (C) 2011 Mobileer Inc + */ +public class WaveFileWriter implements AudioOutputStream { + private static final short WAVE_FORMAT_PCM = 1; + private OutputStream outputStream; + private long riffSizePosition = 0; + private long dataSizePosition = 0; + private int frameRate = 44100; + private int samplesPerFrame = 1; + private int bitsPerSample = 16; + private int bytesWritten; + private File outputFile; + private boolean headerWritten = false; + private final static int PCM24_MIN = -(1 << 23); + private final static int PCM24_MAX = (1 << 23) - 1; + + /** + * Create a writer that will write to the specified file. + * + * @param outputFile + * @throws FileNotFoundException + */ + public WaveFileWriter(File outputFile) throws FileNotFoundException { + this.outputFile = outputFile; + FileOutputStream fileOut = new FileOutputStream(outputFile); + outputStream = new BufferedOutputStream(fileOut); + } + + /** + * @param frameRate default is 44100 + */ + public void setFrameRate(int frameRate) { + this.frameRate = frameRate; + } + + public int getFrameRate() { + return frameRate; + } + + /** For stereo, set this to 2. Default is 1. */ + public void setSamplesPerFrame(int samplesPerFrame) { + this.samplesPerFrame = samplesPerFrame; + } + + public int getSamplesPerFrame() { + return samplesPerFrame; + } + + /** Only 16 or 24 bit samples supported at the moment. Default is 16. */ + public void setBitsPerSample(int bits) { + if ((bits != 16) && (bits != 24)) { + throw new IllegalArgumentException("Only 16 or 24 bits per sample allowed. Not " + bits); + } + bitsPerSample = bits; + } + + public int getBitsPerSample() { + return bitsPerSample; + } + + @Override + public void close() throws IOException { + outputStream.close(); + fixSizes(); + } + + /** Write entire buffer of audio samples to the WAV file. */ + @Override + public void write(double[] buffer) throws IOException { + write(buffer, 0, buffer.length); + } + + /** Write audio to the WAV file. */ + public void write(float[] buffer) throws IOException { + write(buffer, 0, buffer.length); + } + + /** Write single audio data value to the WAV file. */ + @Override + public void write(double value) throws IOException { + if (!headerWritten) { + writeHeader(); + } + + if (bitsPerSample == 24) { + writePCM24(value); + } else { + writePCM16(value); + } + } + + private void writePCM24(double value) throws IOException { + // Offset before casting so that we can avoid using floor(). + // Also round by adding 0.5 so that very small signals go to zero. + double temp = (PCM24_MAX * value) + 0.5 - PCM24_MIN; + int sample = ((int) temp) + PCM24_MIN; + // clip to 24-bit range + if (sample > PCM24_MAX) { + sample = PCM24_MAX; + } else if (sample < PCM24_MIN) { + sample = PCM24_MIN; + } + // encode as little-endian + writeByte(sample); // little end + writeByte(sample >> 8); // middle + writeByte(sample >> 16); // big end + } + + private void writePCM16(double value) throws IOException { + // Offset before casting so that we can avoid using floor(). + // Also round by adding 0.5 so that very small signals go to zero. + double temp = (Short.MAX_VALUE * value) + 0.5 - Short.MIN_VALUE; + int sample = ((int) temp) + Short.MIN_VALUE; + if (sample > Short.MAX_VALUE) { + sample = Short.MAX_VALUE; + } else if (sample < Short.MIN_VALUE) { + sample = Short.MIN_VALUE; + } + writeByte(sample); // little end + writeByte(sample >> 8); // big end + } + + /** Write audio to the WAV file. */ + @Override + public void write(double[] buffer, int start, int count) throws IOException { + for (int i = 0; i < count; i++) { + write(buffer[start + i]); + } + } + + /** Write audio to the WAV file. */ + public void write(float[] buffer, int start, int count) throws IOException { + for (int i = 0; i < count; i++) { + write(buffer[start + i]); + } + } + + // Write lower 8 bits. Upper bits ignored. + private void writeByte(int b) throws IOException { + outputStream.write(b); + bytesWritten += 1; + } + + /** + * Write a 32 bit integer to the stream in Little Endian format. + */ + public void writeIntLittle(int n) throws IOException { + writeByte(n); + writeByte(n >> 8); + writeByte(n >> 16); + writeByte(n >> 24); + } + + /** + * Write a 16 bit integer to the stream in Little Endian format. + */ + public void writeShortLittle(short n) throws IOException { + writeByte(n); + writeByte(n >> 8); + } + + /** + * Write a simple WAV header for PCM data. + */ + private void writeHeader() throws IOException { + writeRiffHeader(); + writeFormatChunk(); + writeDataChunkHeader(); + outputStream.flush(); + headerWritten = true; + } + + /** + * Write a 'RIFF' file header and a 'WAVE' ID to the WAV file. + */ + private void writeRiffHeader() throws IOException { + writeByte('R'); + writeByte('I'); + writeByte('F'); + writeByte('F'); + riffSizePosition = bytesWritten; + writeIntLittle(Integer.MAX_VALUE); + writeByte('W'); + writeByte('A'); + writeByte('V'); + writeByte('E'); + } + + /** + * Write an 'fmt ' chunk to the WAV file containing the given information. + */ + public void writeFormatChunk() throws IOException { + int bytesPerSample = (bitsPerSample + 7) / 8; + + writeByte('f'); + writeByte('m'); + writeByte('t'); + writeByte(' '); + writeIntLittle(16); // chunk size + writeShortLittle(WAVE_FORMAT_PCM); + writeShortLittle((short) samplesPerFrame); + writeIntLittle(frameRate); + // bytes/second + writeIntLittle(frameRate * samplesPerFrame * bytesPerSample); + // block align + writeShortLittle((short) (samplesPerFrame * bytesPerSample)); + writeShortLittle((short) bitsPerSample); + } + + /** + * Write a 'data' chunk header to the WAV file. This should be followed by call to + * writeShortLittle() to write the data to the chunk. + */ + public void writeDataChunkHeader() throws IOException { + writeByte('d'); + writeByte('a'); + writeByte('t'); + writeByte('a'); + dataSizePosition = bytesWritten; + writeIntLittle(Integer.MAX_VALUE); // size + } + + /** + * Fix RIFF and data chunk sizes based on final size. Assume data chunk is the last chunk. + */ + private void fixSizes() throws IOException { + RandomAccessFile randomFile = new RandomAccessFile(outputFile, "rw"); + try { + // adjust RIFF size + long end = bytesWritten; + int riffSize = (int) (end - riffSizePosition) - 4; + randomFile.seek(riffSizePosition); + writeRandomIntLittle(randomFile, riffSize); + // adjust data size + int dataSize = (int) (end - dataSizePosition) - 4; + randomFile.seek(dataSizePosition); + writeRandomIntLittle(randomFile, dataSize); + } finally { + randomFile.close(); + } + } + + private void writeRandomIntLittle(RandomAccessFile randomFile, int n) throws IOException { + byte[] buffer = new byte[4]; + buffer[0] = (byte) n; + buffer[1] = (byte) (n >> 8); + buffer[2] = (byte) (n >> 16); + buffer[3] = (byte) (n >> 24); + randomFile.write(buffer); + } + +} diff --git a/src/main/java/com/jsyn/util/WaveRecorder.java b/src/main/java/com/jsyn/util/WaveRecorder.java new file mode 100644 index 0000000..8008d1d --- /dev/null +++ b/src/main/java/com/jsyn/util/WaveRecorder.java @@ -0,0 +1,134 @@ +/* + * Copyright 2011 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.util; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; + +import com.jsyn.Synthesizer; +import com.jsyn.ports.UnitInputPort; + +/** + * Connect a unit generator to the input. Then start() recording. The signal will be written to a + * WAV format file that can be read by other programs. + * + * @author Phil Burk (C) 2011 Mobileer Inc + */ +public class WaveRecorder { + private AudioStreamReader reader; + private WaveFileWriter writer; + private StreamingThread thread; + private Synthesizer synth; + private TransportModel transportModel = new TransportModel(); + private double maxRecordingTime; + + /** + * Create a stereo 16-bit recorder. + * + * @param synth + * @param outputFile + * @throws FileNotFoundException + */ + public WaveRecorder(Synthesizer synth, File outputFile) throws FileNotFoundException { + this(synth, outputFile, 2, 16); + } + + public WaveRecorder(Synthesizer synth, File outputFile, int samplesPerFrame) + throws FileNotFoundException { + this(synth, outputFile, samplesPerFrame, 16); + } + + /** + * @param synth + * @param outputFile + * @param samplesPerFrame 1 for mono, 2 for stereo + * @param bitsPerSample 16 or 24 + * @throws FileNotFoundException + */ + public WaveRecorder(Synthesizer synth, File outputFile, int samplesPerFrame, int bitsPerSample) + throws FileNotFoundException { + this.synth = synth; + reader = new AudioStreamReader(synth, samplesPerFrame); + + writer = new WaveFileWriter(outputFile); + writer.setFrameRate(synth.getFrameRate()); + writer.setSamplesPerFrame(samplesPerFrame); + writer.setBitsPerSample(bitsPerSample); + } + + public UnitInputPort getInput() { + return reader.getInput(); + } + + public void start() { + stop(); + thread = new StreamingThread(reader, writer); + thread.setTransportModel(transportModel); + thread.setSamplesPerFrame(writer.getSamplesPerFrame()); + updateMaxRecordingTime(); + thread.start(); + } + + public void stop() { + if (thread != null) { + thread.requestStop(); + try { + thread.join(500); + } catch (InterruptedException ignored) { + } + thread = null; + } + } + + /** Close and disconnect any connected inputs. */ + public void close() throws IOException { + stop(); + if (writer != null) { + writer.close(); + writer = null; + } + if (reader != null) { + reader.close(); + for (int i = 0; i < reader.getInput().getNumParts(); i++) { + reader.getInput().disconnectAll(i); + } + reader = null; + } + } + + public void addTransportListener(TransportListener listener) { + transportModel.addTransportListener(listener); + } + + public void removeTransportListener(TransportListener listener) { + transportModel.removeTransportListener(listener); + } + + public void setMaxRecordingTime(double maxRecordingTime) { + this.maxRecordingTime = maxRecordingTime; + updateMaxRecordingTime(); + } + + private void updateMaxRecordingTime() { + StreamingThread streamingThread = thread; + if (streamingThread != null) { + long maxFrames = (long) (maxRecordingTime * synth.getFrameRate()); + streamingThread.setMaxFrames(maxFrames); + } + } +} diff --git a/src/main/java/com/jsyn/util/soundfile/AIFFFileParser.java b/src/main/java/com/jsyn/util/soundfile/AIFFFileParser.java new file mode 100644 index 0000000..89b443c --- /dev/null +++ b/src/main/java/com/jsyn/util/soundfile/AIFFFileParser.java @@ -0,0 +1,232 @@ +/* + * 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.util.soundfile; + +import java.io.EOFException; +import java.io.IOException; + +import com.jsyn.data.FloatSample; +import com.jsyn.data.SampleMarker; +import com.jsyn.util.SampleLoader; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class AIFFFileParser extends AudioFileParser { + + private static final Logger LOGGER = LoggerFactory.getLogger(AIFFFileParser.class); + + private static final String SUPPORTED_FORMATS = "Only 16 and 24 bit PCM or 32-bit float AIF files supported."; + static final int AIFF_ID = ('A' << 24) | ('I' << 16) | ('F' << 8) | 'F'; + static final int AIFC_ID = ('A' << 24) | ('I' << 16) | ('F' << 8) | 'C'; + static final int COMM_ID = ('C' << 24) | ('O' << 16) | ('M' << 8) | 'M'; + static final int SSND_ID = ('S' << 24) | ('S' << 16) | ('N' << 8) | 'D'; + static final int MARK_ID = ('M' << 24) | ('A' << 16) | ('R' << 8) | 'K'; + static final int INST_ID = ('I' << 24) | ('N' << 16) | ('S' << 8) | 'T'; + static final int NONE_ID = ('N' << 24) | ('O' << 16) | ('N' << 8) | 'E'; + static final int FL32_ID = ('F' << 24) | ('L' << 16) | ('3' << 8) | '2'; + static final int FL32_ID_LC = ('f' << 24) | ('l' << 16) | ('3' << 8) | '2'; + + int sustainBeginID = -1; + int sustainEndID = -1; + int releaseBeginID = -1; + int releaseEndID = -1; + boolean typeFloat = false; + + @Override + FloatSample finish() throws IOException { + setLoops(); + + if ((byteData == null)) { + throw new IOException("No data found in audio sample."); + } + float[] floatData = new float[numFrames * samplesPerFrame]; + if (bitsPerSample == 16) { + SampleLoader.decodeBigI16ToF32(byteData, 0, byteData.length, floatData, 0); + } else if (bitsPerSample == 24) { + SampleLoader.decodeBigI24ToF32(byteData, 0, byteData.length, floatData, 0); + } else if (bitsPerSample == 32) { + if (typeFloat) { + SampleLoader.decodeBigF32ToF32(byteData, 0, byteData.length, floatData, 0); + } else { + SampleLoader.decodeBigI32ToF32(byteData, 0, byteData.length, floatData, 0); + } + } else { + throw new IOException(SUPPORTED_FORMATS + " size = " + bitsPerSample); + } + + return makeSample(floatData); + } + + double read80BitFloat() throws IOException { + /* + * This is not a full decoding of the 80 bit number but it should suffice for the range we + * expect. + */ + byte[] bytes = new byte[10]; + parser.read(bytes); + int exp = ((bytes[0] & 0x3F) << 8) | (bytes[1] & 0xFF); + int mant = ((bytes[2] & 0xFF) << 16) | ((bytes[3] & 0xFF) << 8) | (bytes[4] & 0xFF); + // LOGGER.debug( "exp = " + exp + ", mant = " + mant ); + return mant / (double) (1 << (22 - exp)); + } + + void parseCOMMChunk(IFFParser parser, int ckSize) throws IOException { + samplesPerFrame = parser.readShortBig(); + numFrames = parser.readIntBig(); + bitsPerSample = parser.readShortBig(); + frameRate = read80BitFloat(); + if (ckSize > 18) { + int format = parser.readIntBig(); + // Validate data format. + if ((format == FL32_ID) || (format == FL32_ID_LC)) { + typeFloat = true; + } else if (format == NONE_ID) { + typeFloat = false; + } else { + throw new IOException(SUPPORTED_FORMATS + " format " + IFFParser.IDToString(format)); + } + } + + bytesPerSample = (bitsPerSample + 7) / 8; + bytesPerFrame = bytesPerSample * samplesPerFrame; + } + + /* parse tuning and multi-sample info */ + @SuppressWarnings("unused") + void parseINSTChunk(IFFParser parser, int ckSize) throws IOException { + int baseNote = parser.readByte(); + int detune = parser.readByte(); + originalPitch = baseNote + (0.01 * detune); + + int lowNote = parser.readByte(); + int highNote = parser.readByte(); + + parser.skip(2); /* lo,hi velocity */ + int gain = parser.readShortBig(); + + int playMode = parser.readShortBig(); /* sustain */ + sustainBeginID = parser.readShortBig(); + sustainEndID = parser.readShortBig(); + + playMode = parser.readShortBig(); /* release */ + releaseBeginID = parser.readShortBig(); + releaseEndID = parser.readShortBig(); + } + + private void setLoops() { + SampleMarker cuePoint = cueMap.get(sustainBeginID); + if (cuePoint != null) { + sustainBegin = cuePoint.position; + } + cuePoint = cueMap.get(sustainEndID); + if (cuePoint != null) { + sustainEnd = cuePoint.position; + } + } + + void parseSSNDChunk(IFFParser parser, int ckSize) throws IOException { + long numRead; + // LOGGER.debug("parseSSNDChunk()"); + int offset = parser.readIntBig(); + parser.readIntBig(); /* blocksize */ + parser.skip(offset); + dataPosition = parser.getOffset(); + int numBytes = ckSize - 8 - offset; + if (ifLoadData) { + byteData = new byte[numBytes]; + numRead = parser.read(byteData); + } else { + numRead = parser.skip(numBytes); + } + if (numRead != numBytes) + throw new EOFException("AIFF data chunk too short!"); + } + + void parseMARKChunk(IFFParser parser, int ckSize) throws IOException { + long startOffset = parser.getOffset(); + int numCuePoints = parser.readShortBig(); + // LOGGER.debug( "parseCueChunk: numCuePoints = " + numCuePoints + // ); + for (int i = 0; i < numCuePoints; i++) { + // Some AIF files have a bogus numCuePoints so check to see if we + // are at end. + long numInMark = parser.getOffset() - startOffset; + if (numInMark >= ckSize) { + LOGGER.debug("Reached end of MARK chunk with bogus numCuePoints = " + + numCuePoints); + break; + } + + int uniqueID = parser.readShortBig(); + int position = parser.readIntBig(); + int len = parser.read(); + String markerName = parseString(parser, len); + if ((len & 1) == 0) { + parser.skip(1); /* skip pad byte */ + } + + SampleMarker cuePoint = findOrCreateCuePoint(uniqueID); + cuePoint.position = position; + cuePoint.name = markerName; + + if (IFFParser.debug) { + LOGGER.debug("AIFF Marker at " + position + ", " + markerName); + } + } + } + + /** + * Called by parse() method to handle FORM chunks in an AIFF specific manner. + * + * @param ckID four byte chunk ID such as 'data' + * @param ckSize size of chunk in bytes + * @exception IOException If parsing fails, or IO error occurs. + */ + @Override + public void handleForm(IFFParser parser, int ckID, int ckSize, int type) throws IOException { + if ((ckID == IFFParser.FORM_ID) && (type != AIFF_ID) && (type != AIFC_ID)) + throw new IOException("Bad AIFF form type = " + IFFParser.IDToString(type)); + } + + /** + * Called by parse() method to handle chunks in an AIFF specific manner. + * + * @param ckID four byte chunk ID such as 'data' + * @param ckSize size of chunk in bytes + * @exception IOException If parsing fails, or IO error occurs. + */ + @Override + public void handleChunk(IFFParser parser, int ckID, int ckSize) throws IOException { + switch (ckID) { + case COMM_ID: + parseCOMMChunk(parser, ckSize); + break; + case SSND_ID: + parseSSNDChunk(parser, ckSize); + break; + case MARK_ID: + parseMARKChunk(parser, ckSize); + break; + case INST_ID: + parseINSTChunk(parser, ckSize); + break; + default: + break; + } + } + +} diff --git a/src/main/java/com/jsyn/util/soundfile/AudioFileParser.java b/src/main/java/com/jsyn/util/soundfile/AudioFileParser.java new file mode 100644 index 0000000..e7bb066 --- /dev/null +++ b/src/main/java/com/jsyn/util/soundfile/AudioFileParser.java @@ -0,0 +1,129 @@ +/* + * Copyright 2001 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.util.soundfile; + +import java.io.IOException; +import java.util.HashMap; + +import com.jsyn.data.FloatSample; +import com.jsyn.data.SampleMarker; + +/** + * Base class for various types of audio specific file parsers. + * + * @author (C) 2001 Phil Burk, SoftSynth.com + */ + +abstract class AudioFileParser implements ChunkHandler { + IFFParser parser; + protected byte[] byteData; + boolean ifLoadData = true; /* If true, load sound data into memory. */ + long dataPosition; /* + * Number of bytes from beginning of file where sound data resides. + */ + protected int bitsPerSample; + protected int bytesPerFrame; // in the file + protected int bytesPerSample; // in the file + protected HashMap cueMap = new HashMap(); + protected short samplesPerFrame; + protected double frameRate; + protected int numFrames; + protected double originalPitch = 60.0; + protected int sustainBegin = -1; + protected int sustainEnd = -1; + + public AudioFileParser() { + } + + /** + * @return Number of bytes from beginning of stream where sound data resides. + */ + public long getDataPosition() { + return dataPosition; + } + + /** + * This can be read by another thread when load()ing a sample to determine how many bytes have + * been read so far. + */ + public synchronized long getNumBytesRead() { + IFFParser p = parser; // prevent race + if (p != null) + return p.getOffset(); + else + return 0; + } + + /** + * This can be read by another thread when load()ing a sample to determine how many bytes need + * to be read. + */ + public synchronized long getFileSize() { + IFFParser p = parser; // prevent race + if (p != null) + return p.getFileSize(); + else + return 0; + } + + protected SampleMarker findOrCreateCuePoint(int uniqueID) { + SampleMarker cuePoint = cueMap.get(uniqueID); + if (cuePoint == null) { + cuePoint = new SampleMarker(); + cueMap.put(uniqueID, cuePoint); + } + return cuePoint; + } + + public FloatSample load(IFFParser parser) throws IOException { + this.parser = parser; + parser.parseAfterHead(this); + return finish(); + } + + abstract FloatSample finish() throws IOException; + + FloatSample makeSample(float[] floatData) throws IOException { + FloatSample floatSample = new FloatSample(floatData, samplesPerFrame); + + floatSample.setChannelsPerFrame(samplesPerFrame); + floatSample.setFrameRate(frameRate); + floatSample.setPitch(originalPitch); + + if (sustainBegin >= 0) { + floatSample.setSustainBegin(sustainBegin); + floatSample.setSustainEnd(sustainEnd); + } + + for (SampleMarker marker : cueMap.values()) { + floatSample.addMarker(marker); + } + + /* Set Sustain Loop by assuming first two markers are loop points. */ + if (floatSample.getMarkerCount() >= 2) { + floatSample.setSustainBegin(floatSample.getMarker(0).position); + floatSample.setSustainEnd(floatSample.getMarker(1).position); + } + return floatSample; + } + + protected String parseString(IFFParser parser, int textLength) throws IOException { + byte[] bar = new byte[textLength]; + parser.read(bar); + return new String(bar); + } +} diff --git a/src/main/java/com/jsyn/util/soundfile/ChunkHandler.java b/src/main/java/com/jsyn/util/soundfile/ChunkHandler.java new file mode 100644 index 0000000..6dfe26d --- /dev/null +++ b/src/main/java/com/jsyn/util/soundfile/ChunkHandler.java @@ -0,0 +1,49 @@ +/* + * Copyright 1997 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.util.soundfile; + +import java.io.IOException; + +/** + * Handle IFF Chunks as they are parsed from an IFF or RIFF file. + * + * @see IFFParser + * @see AudioSampleAIFF + * @author (C) 1997 Phil Burk, SoftSynth.com + */ +interface ChunkHandler { + /** + * The parser will call this when it encounters a FORM or LIST chunk that contains other chunks. + * This handler can either read the form's chunks, or let the parser find them and call + * handleChunk(). + * + * @param ID a 4 byte identifier such as FORM_ID that identifies the IFF chunk type. + * @param numBytes number of bytes contained in the FORM, not counting the FORM type. + * @param type a 4 byte identifier such as AIFF_ID that identifies the FORM type. + */ + public void handleForm(IFFParser parser, int ID, int numBytes, int type) throws IOException; + + /** + * The parser will call this when it encounters a chunk that is not a FORM or LIST. This handler + * can either read the chunk's, or ignore it. The parser will skip over any unread data. Do NOT + * read past the end of the chunk! + * + * @param ID a 4 byte identifier such as SSND_ID that identifies the IFF chunk type. + * @param numBytes number of bytes contained in the chunk, not counting the ID and size field. + */ + public void handleChunk(IFFParser parser, int ID, int numBytes) throws IOException; +} diff --git a/src/main/java/com/jsyn/util/soundfile/CustomSampleLoader.java b/src/main/java/com/jsyn/util/soundfile/CustomSampleLoader.java new file mode 100644 index 0000000..14efde9 --- /dev/null +++ b/src/main/java/com/jsyn/util/soundfile/CustomSampleLoader.java @@ -0,0 +1,60 @@ +/* + * 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.util.soundfile; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; + +import com.jsyn.data.FloatSample; +import com.jsyn.util.AudioSampleLoader; + +public class CustomSampleLoader implements AudioSampleLoader { + + @Override + public FloatSample loadFloatSample(File fileIn) throws IOException { + FileInputStream fileStream = new FileInputStream(fileIn); + BufferedInputStream inputStream = new BufferedInputStream(fileStream); + return loadFloatSample(inputStream); + } + + @Override + public FloatSample loadFloatSample(URL url) throws IOException { + InputStream rawStream = url.openStream(); + BufferedInputStream inputStream = new BufferedInputStream(rawStream); + return loadFloatSample(inputStream); + } + + @Override + public FloatSample loadFloatSample(InputStream inputStream) throws IOException { + AudioFileParser fileParser; + IFFParser parser = new IFFParser(inputStream); + parser.readHead(); + if (parser.isRIFF()) { + fileParser = new WAVEFileParser(); + } else if (parser.isIFF()) { + fileParser = new AIFFFileParser(); + } else { + throw new IOException("Unsupported audio file type."); + } + return fileParser.load(parser); + } + +} diff --git a/src/main/java/com/jsyn/util/soundfile/IFFParser.java b/src/main/java/com/jsyn/util/soundfile/IFFParser.java new file mode 100644 index 0000000..9bb4ec3 --- /dev/null +++ b/src/main/java/com/jsyn/util/soundfile/IFFParser.java @@ -0,0 +1,313 @@ +/* + * Copyright 1997 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.util.soundfile; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.EOFException; +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * Parse Electronic Arts style IFF File. IFF is a file format that allows "chunks" of data to be + * placed in a hierarchical file. It was designed by Jerry Morrison at Electronic Arts for the Amiga + * computer and is now used extensively by Apple Computer and other companies. IFF is an open + * standard. + * + * @see RIFFParser + * @see AudioSampleAIFF + * @author (C) 1997 Phil Burk, SoftSynth.com + */ + +class IFFParser extends FilterInputStream { + + private static final Logger LOGGER = LoggerFactory.getLogger(IFFParser.class); + + private long numBytesRead = 0; + private long totalSize = 0; + private int fileId; + static boolean debug = false; + + public static final int RIFF_ID = ('R' << 24) | ('I' << 16) | ('F' << 8) | 'F'; + public static final int LIST_ID = ('L' << 24) | ('I' << 16) | ('S' << 8) | 'T'; + public static final int FORM_ID = ('F' << 24) | ('O' << 16) | ('R' << 8) | 'M'; + + IFFParser(InputStream stream) { + super(stream); + numBytesRead = 0; + } + + /** + * Size of file based on outermost chunk size plus 8. Can be used to report progress when + * loading samples. + * + * @return Number of bytes in outer chunk plus header. + */ + public long getFileSize() { + return totalSize; + } + + /** + * Since IFF files use chunks with explicit size, it is important to keep track of how many + * bytes have been read from the file. Can be used to report progress when loading samples. + * + * @return Number of bytes read from stream, or skipped. + */ + public long getOffset() { + return numBytesRead; + } + + /** @return Next byte from stream. Increment offset by 1. */ + @Override + public int read() throws IOException { + numBytesRead++; + return super.read(); + } + + /** @return Next byte array from stream. Increment offset by len. */ + @Override + public int read(byte[] bar) throws IOException { + return read(bar, 0, bar.length); + } + + /** @return Next byte array from stream. Increment offset by len. */ + @Override + public int read(byte[] bar, int off, int len) throws IOException { + // Reading from a URL can return before all the bytes are available. + // So we keep reading until we get the whole thing. + int cursor = off; + int numLeft = len; + // keep reading data until we get it all + while (numLeft > 0) { + int numRead = super.read(bar, cursor, numLeft); + if (numRead < 0) + return numRead; + cursor += numRead; + numBytesRead += numRead; + numLeft -= numRead; + // LOGGER.debug("read " + numRead + ", cursor = " + cursor + + // ", len = " + len); + } + return cursor - off; + } + + /** @return Skip forward in stream and add numBytes to offset. */ + @Override + public long skip(long numBytes) throws IOException { + numBytesRead += numBytes; + return super.skip(numBytes); + } + + /** Read 32 bit signed integer assuming Big Endian byte order. */ + public int readIntBig() throws IOException { + int result = read() & 0xFF; + result = (result << 8) | (read() & 0xFF); + result = (result << 8) | (read() & 0xFF); + int data = read(); + if (data == -1) + throw new EOFException("readIntBig() - EOF in middle of word at offset " + numBytesRead); + result = (result << 8) | (data & 0xFF); + return result; + } + + /** Read 32 bit signed integer assuming Little Endian byte order. */ + public int readIntLittle() throws IOException { + int result = read() & 0xFF; // LSB + result |= ((read() & 0xFF) << 8); + result |= ((read() & 0xFF) << 16); + int data = read(); + if (data == -1) + throw new EOFException("readIntLittle() - EOF in middle of word at offset " + + numBytesRead); + result |= (data << 24); + return result; + } + + /** Read 16 bit signed short assuming Big Endian byte order. */ + public short readShortBig() throws IOException { + short result = (short) ((read() << 8)); // MSB + int data = read(); + if (data == -1) + throw new EOFException("readShortBig() - EOF in middle of word at offset " + + numBytesRead); + result |= data & 0xFF; + return result; + } + + /** Read 16 bit signed short assuming Little Endian byte order. */ + public short readShortLittle() throws IOException { + short result = (short) (read() & 0xFF); // LSB + int data = read(); // MSB + if (data == -1) + throw new EOFException("readShortLittle() - EOF in middle of word at offset " + + numBytesRead); + result |= data << 8; + return result; + } + + public int readUShortLittle() throws IOException { + return (readShortLittle()) & 0x0000FFFF; + } + + /** Read 8 bit signed byte. */ + public byte readByte() throws IOException { + return (byte) read(); + } + + /** Read 32 bit signed int assuming IFF order. */ + public int readChunkSize() throws IOException { + if (isRIFF()) { + return readIntLittle(); + } + { + return readIntBig(); + } + } + + /** Convert a 4 character IFF ID to a String */ + public static String IDToString(int ID) { + byte bar[] = new byte[4]; + bar[0] = (byte) (ID >> 24); + bar[1] = (byte) (ID >> 16); + bar[2] = (byte) (ID >> 8); + bar[3] = (byte) ID; + return new String(bar); + } + + /** + * Parse the stream after reading the first ID and pass the forms and chunks to the ChunkHandler + */ + public void parseAfterHead(ChunkHandler handler) throws IOException { + int numBytes = readChunkSize(); + totalSize = numBytes + 8; + parseChunk(handler, fileId, numBytes); + if (debug) + LOGGER.debug("parse() ------- end"); + } + + /** + * Parse the FORM and pass the chunks to the ChunkHandler The cursor should be positioned right + * after the type field. + */ + void parseForm(ChunkHandler handler, int ID, int numBytes, int type) throws IOException { + if (debug) { + LOGGER.debug("IFF: parseForm >>>>>>>>>>>>>>>>>> BEGIN"); + } + while (numBytes > 8) { + int ckid = readIntBig(); + int size = readChunkSize(); + numBytes -= 8; + if (debug) { + LOGGER.debug("chunk( " + IDToString(ckid) + ", " + size + " )"); + } + if (size < 0) { + throw new IOException("Bad IFF chunk Size: " + IDToString(ckid) + " = 0x" + + Integer.toHexString(ckid) + ", Size = " + size); + } + parseChunk(handler, ckid, size); + if ((size & 1) == 1) + size++; // even-up + numBytes -= size; + if (debug) { + LOGGER.debug("parseForm: numBytes left in form = " + numBytes); + } + } + if (debug) { + LOGGER.debug("IFF: parseForm <<<<<<<<<<<<<<<<<<<< END"); + } + + if (numBytes > 0) { + LOGGER.debug("IFF Parser detected " + numBytes + + " bytes of garbage at end of FORM."); + skip(numBytes); + } + } + + /* + * Parse one chunk from IFF file. After calling handler, make sure stream is positioned at end + * of chunk. + */ + void parseChunk(ChunkHandler handler, int ckid, int numBytes) throws IOException { + long startOffset, endOffset; + int numRead; + startOffset = getOffset(); + if (isForm(ckid)) { + int type = readIntBig(); + if (debug) + LOGGER.debug("parseChunk: form = " + IDToString(ckid) + ", " + numBytes + + ", " + IDToString(type)); + handler.handleForm(this, ckid, numBytes - 4, type); + endOffset = getOffset(); + numRead = (int) (endOffset - startOffset); + if (numRead < numBytes) + parseForm(handler, ckid, (numBytes - numRead), type); + } else { + handler.handleChunk(this, ckid, numBytes); + } + endOffset = getOffset(); + numRead = (int) (endOffset - startOffset); + if (debug) { + LOGGER.debug("parseChunk: endOffset = " + endOffset); + LOGGER.debug("parseChunk: numRead = " + numRead); + } + if ((numBytes & 1) == 1) + numBytes++; // even-up + if (numRead < numBytes) + skip(numBytes - numRead); + } + + public void readHead() throws IOException { + if (debug) + LOGGER.debug("parse() ------- begin"); + numBytesRead = 0; + fileId = readIntBig(); + } + + public boolean isRIFF() { + return (fileId == RIFF_ID); + } + + public boolean isIFF() { + return (fileId == FORM_ID); + } + + /** + * Does the following chunk ID correspond to a container type like FORM? + */ + public boolean isForm(int ckid) { + if (isRIFF()) { + switch (ckid) { + case LIST_ID: + case RIFF_ID: + return true; + default: + return false; + } + } else { + switch (ckid) { + case LIST_ID: + case FORM_ID: + return true; + default: + return false; + } + } + } + +} diff --git a/src/main/java/com/jsyn/util/soundfile/WAVEFileParser.java b/src/main/java/com/jsyn/util/soundfile/WAVEFileParser.java new file mode 100644 index 0000000..a083961 --- /dev/null +++ b/src/main/java/com/jsyn/util/soundfile/WAVEFileParser.java @@ -0,0 +1,338 @@ +/* + * 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.util.soundfile; + +import java.io.EOFException; +import java.io.IOException; + +import com.jsyn.data.FloatSample; +import com.jsyn.data.SampleMarker; +import com.jsyn.util.SampleLoader; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +class WAVEFileParser extends AudioFileParser implements ChunkHandler { + + private static final Logger LOGGER = LoggerFactory.getLogger(WAVEFileParser.class); + + static final short WAVE_FORMAT_PCM = 1; + static final short WAVE_FORMAT_IEEE_FLOAT = 3; + static final short WAVE_FORMAT_EXTENSIBLE = (short) 0xFFFE; + + static final byte[] KSDATAFORMAT_SUBTYPE_IEEE_FLOAT = { + 3, 0, 0, 0, 0, 0, 16, 0, -128, 0, 0, -86, 0, 56, -101, 113 + }; + static final byte[] KSDATAFORMAT_SUBTYPE_PCM = { + 1, 0, 0, 0, 0, 0, 16, 0, -128, 0, 0, -86, 0, 56, -101, 113 + }; + + static final int WAVE_ID = ('W' << 24) | ('A' << 16) | ('V' << 8) | 'E'; + static final int FMT_ID = ('f' << 24) | ('m' << 16) | ('t' << 8) | ' '; + static final int DATA_ID = ('d' << 24) | ('a' << 16) | ('t' << 8) | 'a'; + static final int CUE_ID = ('c' << 24) | ('u' << 16) | ('e' << 8) | ' '; + static final int FACT_ID = ('f' << 24) | ('a' << 16) | ('c' << 8) | 't'; + static final int SMPL_ID = ('s' << 24) | ('m' << 16) | ('p' << 8) | 'l'; + static final int LTXT_ID = ('l' << 24) | ('t' << 16) | ('x' << 8) | 't'; + static final int LABL_ID = ('l' << 24) | ('a' << 16) | ('b' << 8) | 'l'; + + int samplesPerBlock = 0; + int blockAlign = 0; + private int numFactSamples = 0; + private short format; + + WAVEFileParser() { + } + + @Override + FloatSample finish() throws IOException { + if ((byteData == null)) { + throw new IOException("No data found in audio sample."); + } + float[] floatData = new float[numFrames * samplesPerFrame]; + if (bitsPerSample == 16) { + SampleLoader.decodeLittleI16ToF32(byteData, 0, byteData.length, floatData, 0); + } else if (bitsPerSample == 24) { + SampleLoader.decodeLittleI24ToF32(byteData, 0, byteData.length, floatData, 0); + } else if (bitsPerSample == 32) { + if (format == WAVE_FORMAT_IEEE_FLOAT) { + SampleLoader.decodeLittleF32ToF32(byteData, 0, byteData.length, floatData, 0); + } else if (format == WAVE_FORMAT_PCM) { + SampleLoader.decodeLittleI32ToF32(byteData, 0, byteData.length, floatData, 0); + } else { + throw new IOException("WAV: Unsupported format = " + format); + } + } else { + throw new IOException("WAV: Unsupported bitsPerSample = " + bitsPerSample); + } + + return makeSample(floatData); + } + + // typedef struct { + // long dwIdentifier; + // long dwPosition; + // ID fccChunk; + // long dwChunkStart; + // long dwBlockStart; + // long dwSampleOffset; + // } CuePoint; + + /* Parse various chunks encountered in WAV file. */ + void parseCueChunk(IFFParser parser, int ckSize) throws IOException { + int numCuePoints = parser.readIntLittle(); + if (IFFParser.debug) { + LOGGER.debug("WAV: numCuePoints = " + numCuePoints); + } + if ((ckSize - 4) != (6 * 4 * numCuePoints)) + throw new EOFException("Cue chunk too short!"); + for (int i = 0; i < numCuePoints; i++) { + int dwName = parser.readIntLittle(); /* dwName */ + int position = parser.readIntLittle(); // dwPosition + parser.skip(3 * 4); // fccChunk, dwChunkStart, dwBlockStart + int sampleOffset = parser.readIntLittle(); // dwPosition + + if (IFFParser.debug) { + LOGGER.debug("WAV: parseCueChunk: #" + i + ", dwPosition = " + position + + ", dwName = " + dwName + ", dwSampleOffset = " + sampleOffset); + } + SampleMarker cuePoint = findOrCreateCuePoint(dwName); + cuePoint.position = position; + } + } + + void parseLablChunk(IFFParser parser, int ckSize) throws IOException { + int dwName = parser.readIntLittle(); + int textLength = (ckSize - 4) - 1; // don't read NUL terminator + String text = parseString(parser, textLength); + if (IFFParser.debug) { + LOGGER.debug("WAV: label id = " + dwName + ", text = " + text); + } + SampleMarker cuePoint = findOrCreateCuePoint(dwName); + cuePoint.name = text; + } + + void parseLtxtChunk(IFFParser parser, int ckSize) throws IOException { + int dwName = parser.readIntLittle(); + int dwSampleLength = parser.readIntLittle(); + parser.skip(4 + (4 * 2)); // purpose through codepage + int textLength = (ckSize - ((4 * 4) + (4 * 2))) - 1; // don't read NUL + // terminator + if (textLength > 0) { + String text = parseString(parser, textLength); + if (IFFParser.debug) { + LOGGER.debug("WAV: ltxt id = " + dwName + ", dwSampleLength = " + + dwSampleLength + ", text = " + text); + } + SampleMarker cuePoint = findOrCreateCuePoint(dwName); + cuePoint.comment = text; + } + } + + void parseFmtChunk(IFFParser parser, int ckSize) throws IOException { + format = parser.readShortLittle(); + samplesPerFrame = parser.readShortLittle(); + frameRate = parser.readIntLittle(); + parser.readIntLittle(); /* skip dwAvgBytesPerSec */ + blockAlign = parser.readShortLittle(); + bitsPerSample = parser.readShortLittle(); + + if (IFFParser.debug) { + LOGGER.debug("WAV: format = 0x" + Integer.toHexString(format)); + LOGGER.debug("WAV: bitsPerSample = " + bitsPerSample); + LOGGER.debug("WAV: samplesPerFrame = " + samplesPerFrame); + } + bytesPerFrame = blockAlign; + bytesPerSample = bytesPerFrame / samplesPerFrame; + samplesPerBlock = (8 * blockAlign) / bitsPerSample; + + if (format == WAVE_FORMAT_EXTENSIBLE) { + int extraSize = parser.readShortLittle(); + short validBitsPerSample = parser.readShortLittle(); + int channelMask = parser.readIntLittle(); + byte[] guid = new byte[16]; + parser.read(guid); + if (IFFParser.debug) { + LOGGER.debug("WAV: extraSize = " + extraSize); + LOGGER.debug("WAV: validBitsPerSample = " + validBitsPerSample); + LOGGER.debug("WAV: channelMask = " + channelMask); + System.out.print("guid = {"); + for (int i = 0; i < guid.length; i++) { + System.out.print(guid[i] + ", "); + } + LOGGER.debug("}"); + } + if (matchBytes(guid, KSDATAFORMAT_SUBTYPE_IEEE_FLOAT)) { + format = WAVE_FORMAT_IEEE_FLOAT; + } else if (matchBytes(guid, KSDATAFORMAT_SUBTYPE_PCM)) { + format = WAVE_FORMAT_PCM; + } + } + if ((format != WAVE_FORMAT_PCM) && (format != WAVE_FORMAT_IEEE_FLOAT)) { + throw new IOException( + "Only WAVE_FORMAT_PCM and WAVE_FORMAT_IEEE_FLOAT supported. format = " + format); + } + if ((bitsPerSample != 16) && (bitsPerSample != 24) && (bitsPerSample != 32)) { + throw new IOException( + "Only 16 and 24 bit PCM or 32-bit float WAV files supported. width = " + + bitsPerSample); + } + } + + private boolean matchBytes(byte[] bar1, byte[] bar2) { + if (bar1.length != bar2.length) + return false; + for (int i = 0; i < bar1.length; i++) { + if (bar1[i] != bar2[i]) + return false; + } + return true; + } + + private int convertByteToFrame(int byteOffset) throws IOException { + if (blockAlign == 0) { + throw new IOException("WAV file has bytesPerBlock = zero"); + } + if (samplesPerFrame == 0) { + throw new IOException("WAV file has samplesPerFrame = zero"); + } + return (samplesPerBlock * byteOffset) / (samplesPerFrame * blockAlign); + } + + private int calculateNumFrames(int numBytes) throws IOException { + int nFrames; + if (numFactSamples > 0) { + // nFrames = numFactSamples / samplesPerFrame; + nFrames = numFactSamples; // FIXME which is right + } else { + nFrames = convertByteToFrame(numBytes); + } + return nFrames; + } + + // Read fraction in range of 0 to 0xFFFFFFFF and + // convert to 0.0 to 1.0 range. + private double readFraction(IFFParser parser) throws IOException { + // Put L at end or we get -1. + long maxFraction = 0x0FFFFFFFFL; + // Get unsigned fraction. Have to fit in long. + long fraction = (parser.readIntLittle()) & maxFraction; + return (double) fraction / (double) maxFraction; + } + + void parseSmplChunk(IFFParser parser, int ckSize) throws IOException { + parser.readIntLittle(); // Manufacturer + parser.readIntLittle(); // Product + parser.readIntLittle(); // Sample Period + int unityNote = parser.readIntLittle(); + double pitchFraction = readFraction(parser); + originalPitch = unityNote + pitchFraction; + + parser.readIntLittle(); // SMPTE Format + parser.readIntLittle(); // SMPTE Offset + int numLoops = parser.readIntLittle(); + parser.readIntLittle(); // Sampler Data + + int lastCueID = Integer.MAX_VALUE; + for (int i = 0; i < numLoops; i++) { + int cueID = parser.readIntLittle(); + parser.readIntLittle(); // type + int loopStartPosition = parser.readIntLittle(); + // Point to sample one after. + int loopEndPosition = parser.readIntLittle() + 1; + // TODO handle fractional loop sizes? + double endFraction = readFraction(parser); + parser.readIntLittle(); // playCount + + // Use lowest numbered cue. + if (cueID < lastCueID) { + sustainBegin = loopStartPosition; + sustainEnd = loopEndPosition; + } + } + } + + void parseFactChunk(IFFParser parser, int ckSize) throws IOException { + numFactSamples = parser.readIntLittle(); + } + + void parseDataChunk(IFFParser parser, int ckSize) throws IOException { + long numRead; + dataPosition = parser.getOffset(); + if (ifLoadData) { + byteData = new byte[ckSize]; + numRead = parser.read(byteData); + } else { + numRead = parser.skip(ckSize); + } + if (numRead != ckSize) { + throw new EOFException("WAV data chunk too short! Read " + numRead + " instead of " + + ckSize); + } + numFrames = calculateNumFrames(ckSize); + } + + @Override + public void handleForm(IFFParser parser, int ckID, int ckSize, int type) throws IOException { + if ((ckID == IFFParser.RIFF_ID) && (type != WAVE_ID)) + throw new IOException("Bad WAV form type = " + IFFParser.IDToString(type)); + } + + /** + * Called by parse() method to handle chunks in a WAV specific manner. + * + * @param ckID four byte chunk ID such as 'data' + * @param ckSize size of chunk in bytes + * @return number of bytes left in chunk + */ + @Override + public void handleChunk(IFFParser parser, int ckID, int ckSize) throws IOException { + switch (ckID) { + case FMT_ID: + parseFmtChunk(parser, ckSize); + break; + case DATA_ID: + parseDataChunk(parser, ckSize); + break; + case CUE_ID: + parseCueChunk(parser, ckSize); + break; + case FACT_ID: + parseFactChunk(parser, ckSize); + break; + case SMPL_ID: + parseSmplChunk(parser, ckSize); + break; + case LABL_ID: + parseLablChunk(parser, ckSize); + break; + case LTXT_ID: + parseLtxtChunk(parser, ckSize); + break; + default: + break; + } + } + + /* + * (non-Javadoc) + * @see com.softsynth.javasonics.util.AudioSampleLoader#isLittleEndian() + */ + boolean isLittleEndian() { + return true; + } + +} diff --git a/src/main/java/com/softsynth/math/AudioMath.java b/src/main/java/com/softsynth/math/AudioMath.java new file mode 100644 index 0000000..06eb45b --- /dev/null +++ b/src/main/java/com/softsynth/math/AudioMath.java @@ -0,0 +1,82 @@ +/* + * Copyright 1998 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.softsynth.math; + +/** + * Miscellaneous math functions useful in Audio + * + * @author (C) 1998 Phil Burk + */ +public class AudioMath { + // use scalar to convert natural log to log_base_10 + private final static double a2dScalar = 20.0 / Math.log(10.0); + public static final int CONCERT_A_PITCH = 69; + public static final double CONCERT_A_FREQUENCY = 440.0; + private static double mConcertAFrequency = CONCERT_A_FREQUENCY; + + /** + * Convert amplitude to decibels. 1.0 is zero dB. 0.5 is -6.02 dB. + */ + public static double amplitudeToDecibels(double amplitude) { + return Math.log(amplitude) * a2dScalar; + } + + /** + * Convert decibels to amplitude. Zero dB is 1.0 and -6.02 dB is 0.5. + */ + public static double decibelsToAmplitude(double decibels) { + return Math.pow(10.0, decibels / 20.0); + } + + /** + * Calculate MIDI pitch based on frequency in Hertz. Middle C is 60.0. + */ + public static double frequencyToPitch(double frequency) { + return CONCERT_A_PITCH + 12 * Math.log(frequency / mConcertAFrequency) / Math.log(2.0); + } + + /** + * Calculate frequency in Hertz based on MIDI pitch. Middle C is 60.0. You can use fractional + * pitches so 60.5 would give you a pitch half way between C and C#. + */ + public static double pitchToFrequency(double pitch) { + return mConcertAFrequency * Math.pow(2.0, ((pitch - CONCERT_A_PITCH) * (1.0 / 12.0))); + } + + /** + * This can be used to globally adjust the tuning in JSyn from Concert A at 440.0 Hz to + * a slightly different frequency. Some orchestras use a higher frequency, eg. 441.0. + * This value will be used by pitchToFrequency() and frequencyToPitch(). + * + * @param concertAFrequency + */ + public static void setConcertAFrequency(double concertAFrequency) { + mConcertAFrequency = concertAFrequency; + } + + public static double getConcertAFrequency() { + return mConcertAFrequency; + } + + /** Convert a delta value in semitones to a frequency multiplier. + * @param semitones + * @return scaler For example 2.0 for an input of 12.0 semitones. + */ + public static double semitonesToFrequencyScaler(double semitones) { + return Math.pow(2.0, semitones / 12.0); + } +} diff --git a/src/main/java/com/softsynth/math/ChebyshevPolynomial.java b/src/main/java/com/softsynth/math/ChebyshevPolynomial.java new file mode 100644 index 0000000..bc0e854 --- /dev/null +++ b/src/main/java/com/softsynth/math/ChebyshevPolynomial.java @@ -0,0 +1,45 @@ +/* + * Copyright 1997 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.softsynth.math; + +/** + * ChebyshevPolynomial
+ * Used to generate data for waveshaping table oscillators. + * + * @author Nick Didkovsky (C) 1997 Phil Burk and Nick Didkovsky + */ + +public class ChebyshevPolynomial { + static final Polynomial twoX = new Polynomial(2, 0); + static final Polynomial one = new Polynomial(1); + static final Polynomial oneX = new Polynomial(1, 0); + + /** + * Calculates Chebyshev polynomial of specified integer order. Recursively generated using + * relation Tk+1(x) = 2xTk(x) - Tk-1(x) + * + * @return Chebyshev polynomial of specified order + */ + public static Polynomial T(int order) { + if (order == 0) + return one; + else if (order == 1) + return oneX; + else + return Polynomial.minus(Polynomial.mult(T(order - 1), (twoX)), T(order - 2)); + } +} diff --git a/src/main/java/com/softsynth/math/FourierMath.java b/src/main/java/com/softsynth/math/FourierMath.java new file mode 100644 index 0000000..d133d7f --- /dev/null +++ b/src/main/java/com/softsynth/math/FourierMath.java @@ -0,0 +1,254 @@ +/* + * 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.softsynth.math; + +//Simple Fast Fourier Transform. +public class FourierMath { + static private final int MAX_SIZE_LOG_2 = 16; + static BitReverseTable[] reverseTables = new BitReverseTable[MAX_SIZE_LOG_2]; + static DoubleSineTable[] sineTables = new DoubleSineTable[MAX_SIZE_LOG_2]; + static FloatSineTable[] floatSineTables = new FloatSineTable[MAX_SIZE_LOG_2]; + + private static class DoubleSineTable { + double[] sineValues; + + DoubleSineTable(int numBits) { + int len = 1 << numBits; + sineValues = new double[1 << numBits]; + for (int i = 0; i < len; i++) { + sineValues[i] = Math.sin((i * Math.PI * 2.0) / len); + } + } + } + + private static double[] getDoubleSineTable(int n) { + DoubleSineTable sineTable = sineTables[n]; + if (sineTable == null) { + sineTable = new DoubleSineTable(n); + sineTables[n] = sineTable; + } + return sineTable.sineValues; + } + + private static class FloatSineTable { + float[] sineValues; + + FloatSineTable(int numBits) { + int len = 1 << numBits; + sineValues = new float[1 << numBits]; + for (int i = 0; i < len; i++) { + sineValues[i] = (float) Math.sin((i * Math.PI * 2.0) / len); + } + } + } + + private static float[] getFloatSineTable(int n) { + FloatSineTable sineTable = floatSineTables[n]; + if (sineTable == null) { + sineTable = new FloatSineTable(n); + floatSineTables[n] = sineTable; + } + return sineTable.sineValues; + } + + private static class BitReverseTable { + int[] reversedBits; + + BitReverseTable(int numBits) { + reversedBits = new int[1 << numBits]; + for (int i = 0; i < reversedBits.length; i++) { + reversedBits[i] = reverseBits(i, numBits); + } + } + + static int reverseBits(int index, int numBits) { + int i, rev; + + for (i = rev = 0; i < numBits; i++) { + rev = (rev << 1) | (index & 1); + index >>= 1; + } + + return rev; + } + } + + private static int[] getReverseTable(int n) { + BitReverseTable reverseTable = reverseTables[n]; + if (reverseTable == null) { + reverseTable = new BitReverseTable(n); + reverseTables[n] = reverseTable; + } + return reverseTable.reversedBits; + } + + /** + * Calculate the amplitude of the sine wave associated with each bin of a complex FFT result. + * + * @param ar + * @param ai + * @param magnitudes + */ + public static void calculateMagnitudes(double ar[], double ai[], double[] magnitudes) { + for (int i = 0; i < magnitudes.length; ++i) { + magnitudes[i] = Math.sqrt((ar[i] * ar[i]) + (ai[i] * ai[i])); + } + } + + /** + * Calculate the amplitude of the sine wave associated with each bin of a complex FFT result. + * + * @param ar + * @param ai + * @param magnitudes + */ + public static void calculateMagnitudes(float ar[], float ai[], float[] magnitudes) { + for (int i = 0; i < magnitudes.length; ++i) { + magnitudes[i] = (float) Math.sqrt((ar[i] * ar[i]) + (ai[i] * ai[i])); + } + } + + public static void transform(int sign, int n, double ar[], double ai[]) { + double scale = (sign > 0) ? (2.0 / n) : (0.5); + + int numBits = FourierMath.numBits(n); + int[] reverseTable = getReverseTable(numBits); + double[] sineTable = getDoubleSineTable(numBits); + int mask = n - 1; + int cosineOffset = n / 4; // phase offset between cos and sin + + int i, j; + for (i = 0; i < n; i++) { + j = reverseTable[i]; + if (j >= i) { + double tempr = ar[j] * scale; + double tempi = ai[j] * scale; + ar[j] = ar[i] * scale; + ai[j] = ai[i] * scale; + ar[i] = tempr; + ai[i] = tempi; + } + } + + int mmax, stride; + int numerator = sign * n; + for (mmax = 1, stride = 2 * mmax; mmax < n; mmax = stride, stride = 2 * mmax) { + int phase = 0; + int phaseIncrement = numerator / (2 * mmax); + for (int m = 0; m < mmax; ++m) { + double wr = sineTable[(phase + cosineOffset) & mask]; // cosine + double wi = sineTable[phase]; + + for (i = m; i < n; i += stride) { + j = i + mmax; + double tr = (wr * ar[j]) - (wi * ai[j]); + double ti = (wr * ai[j]) + (wi * ar[j]); + ar[j] = ar[i] - tr; + ai[j] = ai[i] - ti; + ar[i] += tr; + ai[i] += ti; + } + + phase = (phase + phaseIncrement) & mask; + } + mmax = stride; + } + } + + public static void transform(int sign, int n, float ar[], float ai[]) { + float scale = (sign > 0) ? (2.0f / n) : (0.5f); + + int numBits = FourierMath.numBits(n); + int[] reverseTable = getReverseTable(numBits); + float[] sineTable = getFloatSineTable(numBits); + int mask = n - 1; + int cosineOffset = n / 4; // phase offset between cos and sin + + int i, j; + for (i = 0; i < n; i++) { + j = reverseTable[i]; + if (j >= i) { + float tempr = ar[j] * scale; + float tempi = ai[j] * scale; + ar[j] = ar[i] * scale; + ai[j] = ai[i] * scale; + ar[i] = tempr; + ai[i] = tempi; + } + } + + int mmax, stride; + int numerator = sign * n; + for (mmax = 1, stride = 2 * mmax; mmax < n; mmax = stride, stride = 2 * mmax) { + int phase = 0; + int phaseIncrement = numerator / (2 * mmax); + for (int m = 0; m < mmax; ++m) { + float wr = sineTable[(phase + cosineOffset) & mask]; // cosine + float wi = sineTable[phase]; + + for (i = m; i < n; i += stride) { + j = i + mmax; + float tr = (wr * ar[j]) - (wi * ai[j]); + float ti = (wr * ai[j]) + (wi * ar[j]); + ar[j] = ar[i] - tr; + ai[j] = ai[i] - ti; + ar[i] += tr; + ai[i] += ti; + } + + phase = (phase + phaseIncrement) & mask; + } + mmax = stride; + } + } + + /** + * Calculate log2(n) + * + * @param powerOf2 must be a power of two, for example 512 or 1024 + * @return for example, 9 for an input value of 512 + */ + public static int numBits(int powerOf2) { + int i; + assert ((powerOf2 & (powerOf2 - 1)) == 0); // is it a power of 2? + for (i = -1; powerOf2 > 0; powerOf2 = powerOf2 >> 1, i++) + ; + return i; + } + + /** + * Calculate an FFT in place, modifying the input arrays. + * + * @param n + * @param ar + * @param ai + */ + public static void fft(int n, double ar[], double ai[]) { + transform(1, n, ar, ai); // TODO -1 or 1 + } + + /** + * Calculate an inverse FFT in place, modifying the input arrays. + * + * @param n + * @param ar + * @param ai + */ + public static void ifft(int n, double ar[], double ai[]) { + transform(-1, n, ar, ai); // TODO -1 or 1 + } +} diff --git a/src/main/java/com/softsynth/math/JustRatio.java b/src/main/java/com/softsynth/math/JustRatio.java new file mode 100644 index 0000000..f4070b4 --- /dev/null +++ b/src/main/java/com/softsynth/math/JustRatio.java @@ -0,0 +1,47 @@ +/* + * 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.softsynth.math; + +public class JustRatio { + public long numerator; + public long denominator; + + public JustRatio(long numerator, long denominator) { + this.numerator = numerator; + this.denominator = denominator; + } + + public JustRatio(int numerator, int denominator) { + this.numerator = numerator; + this.denominator = denominator; + } + + public double getValue() { + return (double) numerator / denominator; + } + + public void invert() { + long temp = denominator; + denominator = numerator; + numerator = temp; + } + + @Override + public String toString() { + return numerator + "/" + denominator; + } +} diff --git a/src/main/java/com/softsynth/math/Polynomial.java b/src/main/java/com/softsynth/math/Polynomial.java new file mode 100644 index 0000000..6c6f96a --- /dev/null +++ b/src/main/java/com/softsynth/math/Polynomial.java @@ -0,0 +1,259 @@ +/* + * Copyright 1997 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.softsynth.math; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Vector; + +/** + * Polynomial
+ * Implement polynomial using Vector as coefficient holder. Element index is power of X, value at a + * given index is coefficient.
+ *
+ * + * @author Nick Didkovsky, (C) 1997 Phil Burk and Nick Didkovsky + */ + +public class Polynomial { + + private static final Logger LOGGER = LoggerFactory.getLogger(Polynomial.class); + + private final Vector terms; + + // TODO: Does this need to exist? + static class DoubleHolder { + double value; + + public DoubleHolder(double val) { + value = val; + } + + public double get() { + return value; + } + + public void set(double val) { + value = val; + } + } + + /** create a polynomial with no terms */ + public Polynomial() { + terms = new Vector(); + } + + /** create a polynomial with one term of specified constant */ + public Polynomial(double c0) { + this(); + appendTerm(c0); + } + + /** create a polynomial with two terms with specified coefficients */ + public Polynomial(double c1, double c0) { + this(c0); + appendTerm(c1); + } + + /** create a polynomial with specified coefficients */ + public Polynomial(double c2, double c1, double c0) { + this(c1, c0); + appendTerm(c2); + } + + /** create a polynomial with specified coefficients */ + public Polynomial(double c3, double c2, double c1, double c0) { + this(c2, c1, c0); + appendTerm(c3); + } + + /** create a polynomial with specified coefficients */ + public Polynomial(double c4, double c3, double c2, double c1, double c0) { + this(c3, c2, c1, c0); + appendTerm(c4); + } + + /** + * Append a term with specified coefficient. Power will be next available order (ie if the + * polynomial is of order 2, appendTerm will supply the coefficient for x^3 + */ + public void appendTerm(double coefficient) { + terms.addElement(new DoubleHolder(coefficient)); + } + + /** Set the coefficient of given term */ + public void setTerm(double coefficient, int power) { + // If setting a term greater than the current order of the polynomial, pad with zero terms + int size = terms.size(); + if (power >= size) { + for (int i = 0; i < (power - size + 1); i++) { + appendTerm(0); + } + } + ((DoubleHolder) terms.elementAt(power)).set(coefficient); + } + + /** + * Add the coefficient of given term to the specified coefficient. ex. addTerm(3, 1) add 3x to a + * polynomial, addTerm(4, 3) adds 4x^3 + */ + public void addTerm(double coefficient, int power) { + setTerm(coefficient + get(power), power); + } + + /** @return coefficient of nth term (first term=0) */ + public double get(int power) { + if (power >= terms.size()) + return 0.0; + else + return ((DoubleHolder) terms.elementAt(power)).get(); + } + + /** @return number of terms in this polynomial */ + public int size() { + return terms.size(); + } + + /** + * Add two polynomials together + * + * @return new Polynomial that is the sum of p1 and p2 + */ + public static Polynomial plus(Polynomial p1, Polynomial p2) { + Polynomial sum = new Polynomial(); + for (int i = 0; i < Math.max(p1.size(), p2.size()); i++) { + sum.appendTerm(p1.get(i) + p2.get(i)); + } + return sum; + } + + /** + * Subtract polynomial from another. (First arg - Second arg) + * + * @return new Polynomial p1 - p2 + */ + public static Polynomial minus(Polynomial p1, Polynomial p2) { + Polynomial sum = new Polynomial(); + for (int i = 0; i < Math.max(p1.size(), p2.size()); i++) { + sum.appendTerm(p1.get(i) - p2.get(i)); + } + return sum; + } + + /** + * Multiply two Polynomials + * + * @return new Polynomial that is the product p1 * p2 + */ + + public static Polynomial mult(Polynomial p1, Polynomial p2) { + Polynomial product = new Polynomial(); + for (int i = 0; i < p1.size(); i++) { + for (int j = 0; j < p2.size(); j++) { + product.addTerm(p1.get(i) * p2.get(j), i + j); + } + } + return product; + } + + /** + * Multiply a Polynomial by a scaler + * + * @return new Polynomial that is the product p1 * p2 + */ + + public static Polynomial mult(double scaler, Polynomial p1) { + Polynomial product = new Polynomial(); + for (int i = 0; i < p1.size(); i++) { + product.appendTerm(p1.get(i) * scaler); + } + return product; + } + + /** Evaluate this polynomial for x */ + public double evaluate(double x) { + double result = 0.0; + for (int i = 0; i < terms.size(); i++) { + result += get(i) * Math.pow(x, i); + } + return result; + } + + @Override + public String toString() { + String s = ""; + if (size() == 0) + s = "empty polynomial"; + boolean somethingPrinted = false; + for (int i = size() - 1; i >= 0; i--) { + if (get(i) != 0.0) { + if (somethingPrinted) + s += " + "; + String coeff = ""; + // if (get(i) == (int)(get(i))) + // coeff = (int)(get(i)) + ""; + if ((get(i) != 1.0) || (i == 0)) + coeff += get(i); + if (i == 0) + s += coeff; + else { + String power = ""; + if (i != 1) + power = "^" + i; + s += coeff + "x" + power; + } + somethingPrinted = true; + } + } + return s; + } + + public static void main(String[] args) { + Polynomial p1 = new Polynomial(); + LOGGER.debug("p1=" + p1); + Polynomial p2 = new Polynomial(3); + LOGGER.debug("p2=" + p2); + Polynomial p3 = new Polynomial(2, 3); + LOGGER.debug("p3=" + p3); + Polynomial p4 = new Polynomial(1, 2, 3); + LOGGER.debug("p4=" + p4); + LOGGER.debug("p4*5=" + Polynomial.mult(5.0, p4)); + + LOGGER.debug("{}", p4.evaluate(10)); + + LOGGER.debug("{}", Polynomial.plus(p4, p1)); + LOGGER.debug("{}", Polynomial.minus(p4, p3)); + p4.setTerm(12.2, 5); + LOGGER.debug("{}", p4); + p4.addTerm(0.8, 5); + LOGGER.debug("{}", p4); + p4.addTerm(0.8, 7); + LOGGER.debug("{}", p4); + LOGGER.debug("{}", Polynomial.mult(p3, p2)); + LOGGER.debug("{}", Polynomial.mult(p3, p3)); + LOGGER.debug("{}", Polynomial.mult(p2, p2)); + + Polynomial t2 = new Polynomial(2, 0, -1); // 2x^2-1, Chebyshev Polynomial of order 2 + Polynomial t3 = new Polynomial(4, 0, -3, 0); // 4x^3-3x, Chebyshev Polynomial of order 3 + // Calculate Chebyshev Polynomial of order 4 from relation Tk+1(x) = 2xTk(x) - Tk-1(x) + Polynomial t4 = Polynomial.minus(Polynomial.mult(t3, (new Polynomial(2, 0))), t2); + LOGGER.debug(t2 + "\n" + t3 + "\n" + t4); + // com.softsynth.jmsl.util + + } +} diff --git a/src/main/java/com/softsynth/math/PolynomialTableData.java b/src/main/java/com/softsynth/math/PolynomialTableData.java new file mode 100644 index 0000000..8110151 --- /dev/null +++ b/src/main/java/com/softsynth/math/PolynomialTableData.java @@ -0,0 +1,64 @@ +/* + * Copyright 1997 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.softsynth.math; + +/** + * PolynomialTableData
+ * Provides an array of double[] containing data generated by a polynomial.
+ * This is typically used with ChebyshevPolynomial. Input to Polynomial is -1..1, output is -1..1. + * + * @author Nick Didkovsky (C) 1997 Phil Burk and Nick Didkovsky + * @see ChebyshevPolynomial + * @see Polynomial + */ + +public class PolynomialTableData { + + double[] data; + Polynomial polynomial; + + /** + * Constructor which fills double[numFrames] with Polynomial data -1..1
+ * Note that any Polynomial can plug in here, just make sure output is -1..1 when input ranges + * from -1..1 + */ + public PolynomialTableData(Polynomial polynomial, int numFrames) { + data = new double[numFrames]; + this.polynomial = polynomial; + buildData(); + } + + public double[] getData() { + return data; + } + + void buildData() { + double xInterval = 2.0 / (data.length - 1); // FIXED, added "- 1" + double x; + for (int i = 0; i < data.length; i++) { + x = i * xInterval - 1.0; + data[i] = polynomial.evaluate(x); + // LOGGER.debug("x = " + x + ", p(x) = " + data[i] ); + } + + } + + public static void main(String[] args) { + PolynomialTableData chebData = new PolynomialTableData(ChebyshevPolynomial.T(2), 8); + } + +} diff --git a/src/main/java/com/softsynth/math/PrimeFactors.java b/src/main/java/com/softsynth/math/PrimeFactors.java new file mode 100644 index 0000000..06c0d55 --- /dev/null +++ b/src/main/java/com/softsynth/math/PrimeFactors.java @@ -0,0 +1,244 @@ +/* + * Copyright 2011 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.softsynth.math; + +import java.util.ArrayList; + +/** + * Tool for factoring primes and prime ratios. This class contains a static array of primes + * generated using the Sieve of Eratosthenes. + * + * @author Phil Burk (C) 2011 Mobileer Inc + */ +public class PrimeFactors { + private static final int SIEVE_SIZE = 1000; + private static int[] primes; + private final int[] factors; + + static { + // Use Sieve of Eratosthenes to fill Prime table + boolean[] sieve = new boolean[SIEVE_SIZE]; + ArrayList primeList = new ArrayList(); + int i = 2; + while (i < (SIEVE_SIZE / 2)) { + if (!sieve[i]) { + primeList.add(i); + int multiple = 2 * i; + while (multiple < SIEVE_SIZE) { + sieve[multiple] = true; + multiple += i; + } + } + i += 1; + } + primes = primeListToArray(primeList); + } + + private static int[] primeListToArray(ArrayList primeList) { + int[] primes = new int[primeList.size()]; + for (int i = 0; i < primes.length; i++) { + primes[i] = primeList.get(i); + } + return primes; + } + + public PrimeFactors(int[] factors) { + this.factors = factors; + } + + public PrimeFactors(int numerator, int denominator) { + int[] topFactors = factor(numerator); + int[] bottomFactors = factor(denominator); + factors = subtract(topFactors, bottomFactors); + } + + public PrimeFactors subtract(PrimeFactors pf) { + return new PrimeFactors(subtract(factors, pf.factors)); + } + + public PrimeFactors add(PrimeFactors pf) { + return new PrimeFactors(add(factors, pf.factors)); + } + + public static int[] subtract(int[] factorsA, int[] factorsB) { + int max; + int min; + if (factorsA.length > factorsB.length) { + max = factorsA.length; + min = factorsB.length; + } else { + + min = factorsA.length; + max = factorsB.length; + } + ArrayList primeList = new ArrayList(); + int i; + for (i = 0; i < min; i++) { + primeList.add(factorsA[i] - factorsB[i]); + } + if (factorsA.length > factorsB.length) { + for (; i < max; i++) { + primeList.add(factorsA[i]); + } + } else { + for (; i < max; i++) { + primeList.add(0 - factorsB[i]); + } + } + trimPrimeList(primeList); + return primeListToArray(primeList); + } + + public static int[] add(int[] factorsA, int[] factorsB) { + int max; + int min; + if (factorsA.length > factorsB.length) { + max = factorsA.length; + min = factorsB.length; + } else { + min = factorsA.length; + max = factorsB.length; + } + ArrayList primeList = new ArrayList(); + int i; + for (i = 0; i < min; i++) { + primeList.add(factorsA[i] + factorsB[i]); + } + if (factorsA.length > factorsB.length) { + for (; i < max; i++) { + primeList.add(factorsA[i]); + } + } else if (factorsB.length > factorsA.length) { + for (; i < max; i++) { + primeList.add(factorsB[i]); + } + } + trimPrimeList(primeList); + return primeListToArray(primeList); + } + + private static void trimPrimeList(ArrayList primeList) { + int i; + // trim zero factors off end. + for (i = primeList.size() - 1; i >= 0; i--) { + if (primeList.get(i) == 0) { + primeList.remove(i); + } else { + break; + } + } + } + + public static int[] factor(int n) { + ArrayList primeList = new ArrayList(); + int i = 0; + int p = primes[i]; + int exponent = 0; + while (n > 1) { + // does the prime number divide evenly into n? + int d = n / p; + int m = d * p; + if (m == n) { + n = d; + exponent += 1; + } else { + primeList.add(exponent); + exponent = 0; + i += 1; + p = primes[i]; + } + } + if (exponent > 0) { + primeList.add(exponent); + } + return primeListToArray(primeList); + } + + /** + * Get prime from table. + * + * + * @param n Warning: Do not exceed getPrimeCount()-1. + * @return Nth prime number, the 0th prime is 2 + */ + public static int getPrime(int n) { + return primes[n]; + } + + /** + * @return the number of primes stored in the table + */ + public static int getPrimeCount() { + return primes.length; + } + + public JustRatio getJustRatio() { + long n = 1; + long d = 1; + for (int i = 0; i < factors.length; i++) { + int exponent = factors[i]; + int p = primes[i]; + if (exponent > 0) { + for (int k = 0; k < exponent; k++) { + n = n * p; + } + } else if (exponent < 0) { + exponent = 0 - exponent; + for (int k = 0; k < exponent; k++) { + d = d * p; + } + } + } + return new JustRatio(n, d); + } + + public int[] getFactors() { + return factors.clone(); + } + + @Override + public String toString() { + StringBuffer buffer = new StringBuffer(); + printFactors(buffer, 1); + buffer.append("/"); + printFactors(buffer, -1); + return buffer.toString(); + } + + private void printFactors(StringBuffer buffer, int sign) { + boolean gotSome = false; + for (int i = 0; i < factors.length; i++) { + int pf = factors[i] * sign; + if (pf > 0) { + if (gotSome) + buffer.append('*'); + int prime = primes[i]; + if (pf == 1) { + buffer.append("" + prime); + } else if (pf == 2) { + buffer.append(prime + "*" + prime); + } else if (pf > 2) { + buffer.append("(" + prime + "^" + pf + ")"); + } + gotSome = true; + } + } + if (!gotSome) { + buffer.append("1"); + } + } +} diff --git a/src/main/java/com/softsynth/shared/time/ScheduledCommand.java b/src/main/java/com/softsynth/shared/time/ScheduledCommand.java new file mode 100644 index 0000000..5b600a7 --- /dev/null +++ b/src/main/java/com/softsynth/shared/time/ScheduledCommand.java @@ -0,0 +1,21 @@ +/* + * 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.softsynth.shared.time; + +public interface ScheduledCommand { + public void run(); +} diff --git a/src/main/java/com/softsynth/shared/time/ScheduledQueue.java b/src/main/java/com/softsynth/shared/time/ScheduledQueue.java new file mode 100644 index 0000000..367e4f8 --- /dev/null +++ b/src/main/java/com/softsynth/shared/time/ScheduledQueue.java @@ -0,0 +1,85 @@ +/* + * 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.softsynth.shared.time; + +import java.util.LinkedList; +import java.util.List; +import java.util.SortedMap; +import java.util.TreeMap; + +/** + * Store objects in time sorted order. + */ +public class ScheduledQueue { + private final SortedMap> timeNodes; + + public ScheduledQueue() { + timeNodes = new TreeMap>(); + } + + public boolean isEmpty() { + return timeNodes.isEmpty(); + } + + public synchronized void add(TimeStamp time, T obj) { + List timeList = timeNodes.get(time); + if (timeList == null) { + timeList = new LinkedList(); + timeNodes.put(time, timeList); + } + timeList.add(obj); + } + + public synchronized List removeNextList(TimeStamp time) { + List timeList = null; + if (!timeNodes.isEmpty()) { + TimeStamp lowestTime = timeNodes.firstKey(); + // Is the lowest time before or equal to the specified time. + if (lowestTime.compareTo(time) <= 0) { + timeList = timeNodes.remove(lowestTime); + } + } + return timeList; + } + + public synchronized Object removeNext(TimeStamp time) { + Object next = null; + if (!timeNodes.isEmpty()) { + TimeStamp lowestTime = timeNodes.firstKey(); + // Is the lowest time before or equal to the specified time. + if (lowestTime.compareTo(time) <= 0) { + List timeList = timeNodes.get(lowestTime); + if (timeList != null) { + next = timeList.remove(0); + if (timeList.isEmpty()) { + timeNodes.remove(lowestTime); + } + } + } + } + return next; + } + + public synchronized void clear() { + timeNodes.clear(); + } + + public TimeStamp getNextTime() { + return timeNodes.firstKey(); + } + +} diff --git a/src/main/java/com/softsynth/shared/time/TimeStamp.java b/src/main/java/com/softsynth/shared/time/TimeStamp.java new file mode 100644 index 0000000..6d243ed --- /dev/null +++ b/src/main/java/com/softsynth/shared/time/TimeStamp.java @@ -0,0 +1,56 @@ +/* + * 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.softsynth.shared.time; + +/** + * @author Phil Burk, (C) 2009 Mobileer Inc + */ +public class TimeStamp implements Comparable { + private final double time; + + public TimeStamp(double time) { + this.time = time; + } + + public double getTime() { + return time; + } + + /** + * @return -1 if (this < t2), 0 if equal, or +1 + */ + @Override + public int compareTo(TimeStamp t2) { + if (time < t2.time) + return -1; + else if (time == t2.time) + return 0; + else + return 1; + } + + /** + * Create a new TimeStamp at a relative offset in seconds. + * + * @param delta + * @return earlier or later TimeStamp + */ + public TimeStamp makeRelative(double delta) { + return new TimeStamp(time + delta); + } + +} diff --git a/src/main/resources/log4j.xml b/src/main/resources/log4j.xml new file mode 100644 index 0000000..cc107b1 --- /dev/null +++ b/src/main/resources/log4j.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/src/test/java/com/jsyn/benchmarks/BenchJSyn.java b/src/test/java/com/jsyn/benchmarks/BenchJSyn.java new file mode 100644 index 0000000..017dc99 --- /dev/null +++ b/src/test/java/com/jsyn/benchmarks/BenchJSyn.java @@ -0,0 +1,228 @@ +/* + * Copyright 2013 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.benchmarks; + +import com.jsyn.JSyn; +import com.jsyn.Synthesizer; +import com.jsyn.unitgen.PassThrough; +import com.jsyn.unitgen.PitchDetector; +import com.jsyn.unitgen.SineOscillator; +import com.jsyn.unitgen.SquareOscillator; +import com.jsyn.unitgen.SquareOscillatorBL; +import com.jsyn.unitgen.UnitOscillator; +import com.softsynth.math.FourierMath; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author Phil Burk (C) 2013 Mobileer Inc + */ +public class BenchJSyn { + + private static final Logger LOGGER = LoggerFactory.getLogger(BenchJSyn.class); + + private Synthesizer synth; + private long startTime; + private long endTime; + private PassThrough pass; + + @Test + public void run() { + try { + // Run multiple times to see if HotSpot compiler or cache makes a difference. + for (int i = 0; i < 4; i++) { + benchmark(); + } + } catch (InstantiationException | IllegalAccessException | InterruptedException e) { + e.printStackTrace(); + } + } + + private void benchmark() throws InstantiationException, IllegalAccessException, + InterruptedException { + double realTime = 10.0; + int count = 40; + + // benchFFTDouble(); + // benchFFTFloat(); + /* + * realTime = 20.0; benchmarkOscillator(SawtoothOscillator.class, count, realTime); + * benchmarkOscillator(SawtoothOscillatorDPW.class, count, realTime); + * benchmarkOscillator(SawtoothOscillatorBL.class, count, realTime); + */ + benchmarkOscillator(SquareOscillator.class, count, realTime); + benchmarkOscillator(SquareOscillatorBL.class, count, realTime); + + benchmarkOscillator(SineOscillator.class, count, realTime); + benchmarkPitchDetector(count, realTime); + + } + + public void benchFFTDouble() { + int size = 2048; + int bin = 5; + int count = 20000; + double[] ar = new double[size]; + double[] ai = new double[size]; + double[] magnitudes = new double[size]; + + double amplitude = 1.0; + addSineWave(size, bin, ar, amplitude); + LOGGER.debug("Bench double FFT"); + startTiming(); + for (int i = 0; i < count; i++) { + FourierMath.transform(1, size, ar, ai); + } + + endTiming(FourierMath.class, count, size / (2.0 * 44100)); + FourierMath.calculateMagnitudes(ar, ai, magnitudes); + + assert (magnitudes[bin - 1] < 0.001); + assert (magnitudes[bin] > 0.5); + assert (magnitudes[bin + 1] < 0.001); + + } + + public void benchFFTFloat() { + int size = 2048; + int bin = 5; + int count = 20000; + float[] ar = new float[size]; + float[] ai = new float[size]; + float[] magnitudes = new float[size]; + + float amplitude = 1.0f; + addSineWave(size, bin, ar, amplitude); + + LOGGER.debug("Bench float FFT"); + startTiming(); + for (int i = 0; i < count; i++) { + FourierMath.transform(1, size, ar, ai); + } + + endTiming(FourierMath.class, count, size / (2.0 * 44100)); + FourierMath.calculateMagnitudes(ar, ai, magnitudes); + + assert (magnitudes[bin - 1] < 0.001); + assert (magnitudes[bin] > 0.5); + assert (magnitudes[bin + 1] < 0.001); + + } + + private void addSineWave(int size, int bin, double[] ar, double amplitude) { + double phase = 0.0; + double phaseIncrement = 2.0 * Math.PI * bin / size; + for (int i = 0; i < size; i++) { + ar[i] += Math.sin(phase) * amplitude; + // LOGGER.debug( i + " = " + ar[i] ); + phase += phaseIncrement; + } + } + + private void addSineWave(int size, int bin, float[] ar, float amplitude) { + float phase = 0.0f; + float phaseIncrement = (float) (2.0 * Math.PI * bin / size); + for (int i = 0; i < size; i++) { + ar[i] += (float) Math.sin(phase) * amplitude; + // LOGGER.debug( i + " = " + ar[i] ); + phase += phaseIncrement; + } + } + + private void stopSynth() { + synth.stop(); + } + + private void startSynth() { + synth = JSyn.createSynthesizer(); // Mac + // synth = JSyn.createSynthesizer( new JSynAndroidAudioDevice() ); // Android + synth.setRealTime(false); + pass = new PassThrough(); + synth.add(pass); + synth.start(); + pass.start(); + } + + private void benchmarkOscillator(Class clazz, int count, double realTime) + throws InstantiationException, IllegalAccessException, InterruptedException { + startSynth(); + for (int i = 0; i < count; i++) { + UnitOscillator osc = (UnitOscillator) clazz.newInstance(); + osc.output.connect(pass.input); + synth.add(osc); + } + startTiming(); + synth.sleepFor(realTime); + endTiming(clazz, count, realTime); + stopSynth(); + } + + private void benchmarkPitchDetector(int count, double realTime) throws InstantiationException, + IllegalAccessException, InterruptedException { + startSynth(); + + PitchDetector detector = new PitchDetector(); + synth.add(detector); + double frequency = 198.0; + double period = synth.getFrameRate() / frequency; + // simple harmonic synthesis + for (int i = 0; i < count; i++) { + SineOscillator osc = new SineOscillator(); + synth.add(osc); + osc.frequency.set(frequency * (i + 1)); + osc.amplitude.set(0.5 * (1.0 - (i * 0.2))); + osc.output.connect(detector.input); + } + detector.start(); + startTiming(); + synth.sleepFor(realTime); + endTiming(PitchDetector.class, count, realTime); + + double measuredPeriod = detector.period.getValue(); + double confidence = detector.confidence.getValue(); + LOGGER.debug("period = " + period + ", measured = " + measuredPeriod + + ", confidence = " + confidence); + if (confidence > 0.1) { + assert (Math.abs(measuredPeriod - period) < 0.1); + } + stopSynth(); + } + + private void endTiming(Class clazz, int count, double realTime) { + endTime = System.nanoTime(); + double elapsedTime = (endTime - startTime) * 1E-9; + double percent = 100.0 * elapsedTime / (realTime * count); + System.out.printf("%32s took %5.3f/%d seconds to process %5.4f of audio = %6.3f%c.\n", + clazz.getSimpleName(), elapsedTime, count, realTime, percent, '%'); + } + + private void startTiming() { + startTime = System.nanoTime(); + } + +// /** +// * @param args +// */ +// public static void main(String[] args) { +// new BenchJSyn().run(); +// } + +} diff --git a/src/test/java/com/jsyn/data/TestShortSample.java b/src/test/java/com/jsyn/data/TestShortSample.java new file mode 100644 index 0000000..6132e4e --- /dev/null +++ b/src/test/java/com/jsyn/data/TestShortSample.java @@ -0,0 +1,72 @@ +/* + * 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.data; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * @author Phil Burk, (C) 2009 Mobileer Inc + */ +public class TestShortSample { + + @Test + public void testBytes() { + byte[] bar = { + 18, -3 + }; + short s = (short) ((bar[0] << 8) | (bar[1] & 0xFF)); + assertEquals(0x12FD, s, "A"); + } + + @Test + public void testReadWrite() { + short[] data = { + 123, 456, -789, 111, 20000, -32768, 32767, 0, 9876 + }; + ShortSample sample = new ShortSample(data.length, 1); + assertEquals(data.length, sample.getNumFrames(), "Sample numFrames"); + + // Write and read entire sample. + sample.write(data); + short[] buffer = new short[data.length]; + sample.read(buffer); + + for (int i = 0; i < data.length; i++) { + assertEquals(data[i], buffer[i], "read = write"); + } + + // Write and read part of an array. + short[] partial = { + 333, 444, 555, 666, 777 + }; + + sample.write(2, partial, 1, 3); + sample.read(1, buffer, 1, 5); + + for (int i = 0; i < data.length; i++) { + if ((i >= 2) && (i <= 4)) { + assertEquals(partial[i - 1], buffer[i], "partial"); + } else { + assertEquals(data[i], buffer[i], "read = write"); + } + } + + } + +} diff --git a/src/test/java/com/jsyn/engine/TestAudioOutput.java b/src/test/java/com/jsyn/engine/TestAudioOutput.java new file mode 100644 index 0000000..39e8211 --- /dev/null +++ b/src/test/java/com/jsyn/engine/TestAudioOutput.java @@ -0,0 +1,78 @@ +/* + * 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.engine; + +import java.io.IOException; + +import com.jsyn.devices.AudioDeviceManager; +import com.jsyn.devices.AudioDeviceOutputStream; +import com.jsyn.devices.javasound.JavaSoundAudioDevice; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author Phil Burk, (C) 2009 Mobileer Inc + */ +public class TestAudioOutput { + + private static final Logger LOGGER = LoggerFactory.getLogger(TestAudioOutput.class); + + @Test + public void testMonoSine() throws IOException { + LOGGER.debug("Test mono output."); + final int FRAMES_PER_BUFFER = 128; + final int SAMPLES_PER_FRAME = 1; + double[] buffer = new double[FRAMES_PER_BUFFER * SAMPLES_PER_FRAME]; + AudioDeviceManager audioDevice = new JavaSoundAudioDevice(); + AudioDeviceOutputStream audioOutput = audioDevice.createOutputStream( + audioDevice.getDefaultOutputDeviceID(), 44100, SAMPLES_PER_FRAME); + for (int i = 0; i < FRAMES_PER_BUFFER; i++) { + double angle = (i * Math.PI * 2.0) / FRAMES_PER_BUFFER; + buffer[i] = Math.sin(angle); + } + audioOutput.start(); + for (int i = 0; i < 1000; i++) { + audioOutput.write(buffer); + } + audioOutput.stop(); + + } + + @Test + public void testStereoSine() throws IOException { + LOGGER.debug("Test stereo output."); + final int FRAMES_PER_BUFFER = 128; + final int SAMPLES_PER_FRAME = 2; + double[] buffer = new double[FRAMES_PER_BUFFER * SAMPLES_PER_FRAME]; + AudioDeviceManager audioDevice = new JavaSoundAudioDevice(); + AudioDeviceOutputStream audioOutput = audioDevice.createOutputStream( + audioDevice.getDefaultOutputDeviceID(), 44100, SAMPLES_PER_FRAME); + int bi = 0; + for (int i = 0; i < FRAMES_PER_BUFFER; i++) { + double angle = (i * Math.PI * 2.0) / FRAMES_PER_BUFFER; + buffer[bi++] = Math.sin(angle); + buffer[bi++] = Math.sin(angle); + } + audioOutput.start(); + for (int i = 0; i < 1000; i++) { + audioOutput.write(buffer); + } + audioOutput.stop(); + } + +} diff --git a/src/test/java/com/jsyn/engine/TestDevices.java b/src/test/java/com/jsyn/engine/TestDevices.java new file mode 100644 index 0000000..307880e --- /dev/null +++ b/src/test/java/com/jsyn/engine/TestDevices.java @@ -0,0 +1,75 @@ +/* + * 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.engine; + +import com.jsyn.JSyn; +import com.jsyn.Synthesizer; +import com.jsyn.devices.AudioDeviceFactory; +import com.jsyn.devices.AudioDeviceManager; +import com.jsyn.unitgen.LineIn; +import com.jsyn.unitgen.LineOut; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class TestDevices { + + private static final Logger LOGGER = LoggerFactory.getLogger(TestDevices.class); + + @Test + public void testPassThrough() { + Synthesizer synth; + LineIn lineIn; + LineOut lineOut; + // Create a context for the synthesizer. + synth = JSyn.createSynthesizer(AudioDeviceFactory.createAudioDeviceManager(true)); + // Add an audio input. + synth.add(lineIn = new LineIn()); + // Add an audio output. + synth.add(lineOut = new LineOut()); + // Connect the input to the output. + lineIn.output.connect(0, lineOut.input, 0); + lineIn.output.connect(1, lineOut.input, 1); + + // Both stereo. + int numInputChannels = 2; + int numOutputChannels = 2; + synth.start(44100, AudioDeviceManager.USE_DEFAULT_DEVICE, numInputChannels, + AudioDeviceManager.USE_DEFAULT_DEVICE, numOutputChannels); + + // We only need to start the LineOut. It will pull data from the LineIn. + lineOut.start(); + LOGGER.debug("Audio passthrough started."); + // Sleep a while. + double sleepTime = 2.0; + try { + double time = synth.getCurrentTime(); + // Sleep for a few seconds. + synth.sleepUntil(time + sleepTime); + } catch (InterruptedException e) { + e.printStackTrace(); + } + double synthTime = synth.getCurrentTime(); + assertEquals(synthTime, 0.2, "Time has advanced. " + synthTime); + // Stop everything. + synth.stop(); + LOGGER.debug("All done."); + + } +} diff --git a/src/test/java/com/jsyn/engine/TestEngine.java b/src/test/java/com/jsyn/engine/TestEngine.java new file mode 100644 index 0000000..0ba70d6 --- /dev/null +++ b/src/test/java/com/jsyn/engine/TestEngine.java @@ -0,0 +1,225 @@ +/* + * 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.engine; + +import com.jsyn.unitgen.Add; +import com.jsyn.unitgen.LineOut; +import com.jsyn.unitgen.PitchDetector; +import com.jsyn.unitgen.SineOscillator; +import com.jsyn.unitgen.ZeroCrossingCounter; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class TestEngine { + + private static final Logger LOGGER = LoggerFactory.getLogger(TestEngine.class); + + @Test + public void testInitialization() { + final int DEFAULT_FRAME_RATE = 44100; + SynthesisEngine synthesisEngine = new SynthesisEngine(); + assertEquals(0, synthesisEngine.getFrameCount(), "frameCount zero before starting"); + assertEquals(DEFAULT_FRAME_RATE, synthesisEngine.getFrameRate(), "default frameRate"); + assertTrue(synthesisEngine.isPullDataEnabled(), "default pullData"); + } + + public void checkPullData(boolean pullData) { + SynthesisEngine synthesisEngine = new SynthesisEngine(); + assertTrue(synthesisEngine.isRealTime(), "default realTime"); + synthesisEngine.setRealTime(false); + + assertTrue(synthesisEngine.isPullDataEnabled(), "default pullData"); + synthesisEngine.setPullDataEnabled(pullData); + + SineOscillator sineOscillator = new SineOscillator(); + synthesisEngine.add(sineOscillator); + + LineOut lineOut = new LineOut(); + synthesisEngine.add(lineOut); + sineOscillator.output.connect(0, lineOut.input, 0); + + assertEquals(0.0, sineOscillator.output.getValue(), "initial sine value"); + + synthesisEngine.start(); + if (!pullData) { + sineOscillator.start(); + } + // We always have to start the LineOut. + lineOut.start(); + synthesisEngine.generateNextBuffer(); + synthesisEngine.generateNextBuffer(); + + double value = sineOscillator.output.getValue(); + assertTrue(value > 0.0, "sine value after generation = " + value); + } + + @Test + public void testPullDataFalse() { + checkPullData(false); + } + + @Test + public void testPullDataTrue() { + checkPullData(true); + } + + @Test + public void testMixedAdding() { + boolean gotCaught = false; + SynthesisEngine synthesisEngine1 = new SynthesisEngine(); + synthesisEngine1.setRealTime(false); + synthesisEngine1.setPullDataEnabled(true); + SynthesisEngine synthesisEngine2 = new SynthesisEngine(); + synthesisEngine2.setRealTime(false); + synthesisEngine2.setPullDataEnabled(true); + + // Create a sineOscillator but do not add it to the synth! + SineOscillator sineOscillator = new SineOscillator(); + LineOut lineOut = new LineOut(); + + synthesisEngine1.add(lineOut); + synthesisEngine2.add(sineOscillator); + try { + sineOscillator.output.connect(0, lineOut.input, 0); + } catch (RuntimeException e) { + gotCaught = true; + assertTrue(e.getMessage().contains("different synths"), "informative MPE message"); + } + + assertTrue(gotCaught, "caught NPE caused by forgetting synth.add"); + } + + @Test + public void testNotAdding() { + SynthesisEngine synthesisEngine = new SynthesisEngine(); + synthesisEngine.setRealTime(false); + synthesisEngine.setPullDataEnabled(true); + + // Create a sineOscillator but do not add it to the synth! + SineOscillator sineOscillator = new SineOscillator(); + + LineOut lineOut = new LineOut(); + sineOscillator.output.connect(0, lineOut.input, 0); + synthesisEngine.add(lineOut); + + assertEquals(0.0, sineOscillator.output.getValue(), "initial sine value"); + + synthesisEngine.start(); + // We always have to start the LineOut. + lineOut.start(); + boolean gotCaught = false; + try { + synthesisEngine.generateNextBuffer(); + synthesisEngine.generateNextBuffer(); + } catch (NullPointerException e) { + gotCaught = true; + assertTrue(e.getMessage().contains("forgot to add"), "informative MPE message"); + } + + assertTrue(gotCaught, "caught NPE caused by forgetting synth.add"); + } + + @Test + public void testMultipleStarts() throws InterruptedException { + SynthesisEngine synth = new SynthesisEngine(); + + // Create a sineOscillator but do not add it to the synth! + SineOscillator osc = new SineOscillator(); + ZeroCrossingCounter counter = new ZeroCrossingCounter(); + PitchDetector pitchDetector = new PitchDetector(); + LineOut lineOut = new LineOut(); + synth.add(osc); + synth.add(counter); + synth.add(lineOut); + synth.add(pitchDetector); + osc.output.connect(counter.input); + osc.output.connect(pitchDetector.input); + counter.output.connect(0, lineOut.input, 0); + + assertEquals(0, counter.getCount(), "initial count"); + + int[] rates = { + 32000, 48000, 44100, 22050 + }; + for (int rate : rates) { + synth.start(rate); + lineOut.start(); + pitchDetector.start(); + + double time = synth.getCurrentTime(); + double interval = 1.0; + time += interval; + + long previousFrameCount = counter.getCount(); + synth.sleepUntil(time); + + double frequencyMeasured = pitchDetector.frequency.get(); + double confidenceMeasured = pitchDetector.confidence.get(); + double oscFreq = osc.frequency.get(); + String msg = "freq at " + rate + " Hz"; + LOGGER.debug(msg); + assertEquals(oscFreq, frequencyMeasured, oscFreq * 0.1, msg); + assertEquals(0.9, confidenceMeasured, 0.1, "pitch confidence"); + + double expectedCount = interval * oscFreq; + double framesMeasured = counter.getCount() - previousFrameCount; + msg = "count at " + rate + " Hz"; + LOGGER.debug(msg); + assertEquals(expectedCount, framesMeasured, expectedCount * 0.1, msg); + + synth.stop(); + } + + } + + @Test + public void testScheduler() throws InterruptedException { + SynthesisEngine synth = new SynthesisEngine(); + synth.setRealTime(false); + Add adder = new Add(); + synth.add(adder); + synth.start(); + adder.start(); + adder.inputA.set(4.0); + adder.inputB.set(10.0); + synth.sleepFor(0.1); + assertEquals(14.0, adder.output.get(), 0.01, "simple add"); + + // Schedule a set() in the near future. + double time = synth.getCurrentTime(); + adder.inputA.set(7.0, time + 1.0); + synth.sleepFor(0.5); + assertEquals(14.0, adder.output.get(), 0.01, "before scheduled set"); + synth.sleepFor(1.0); + assertEquals(17.0, adder.output.get(), 0.01, "after scheduled set"); + + // Schedule a set() in the near future then cancel it. + time = synth.getCurrentTime(); + adder.inputA.set(5.0, time + 1.0); + synth.sleepFor(0.5); + assertEquals(17.0, adder.output.get(), 0.01, "before scheduled set"); + synth.clearCommandQueue(); + synth.sleepFor(1.0); + assertEquals(17.0, adder.output.get(), 0.01, "after canceled set"); + + synth.stop(); + } +} diff --git a/src/test/java/com/jsyn/engine/TestFifo.java b/src/test/java/com/jsyn/engine/TestFifo.java new file mode 100644 index 0000000..d057e19 --- /dev/null +++ b/src/test/java/com/jsyn/engine/TestFifo.java @@ -0,0 +1,245 @@ +/* + * 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.engine; + +import com.jsyn.io.AudioFifo; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.fail; + +public class TestFifo { + + @Test + public void testBasic() { + Thread watchdog = startWatchdog(600); + + AudioFifo fifo = new AudioFifo(); + fifo.setReadWaitEnabled(false); + fifo.allocate(8); + assertEquals(0, fifo.available(), "start empty"); + + assertEquals(Double.NaN, fifo.read(), "read back Nan when emopty"); + + fifo.write(1.0); + assertEquals(1, fifo.available(), "added one value"); + assertEquals(1.0, fifo.read(), "read back same value"); + assertEquals(0, fifo.available(), "back to empty"); + + for (int i = 0; i < fifo.size(); i++) { + assertEquals(i, fifo.available(), "adding data"); + fifo.write(100.0 + i); + } + for (int i = 0; i < fifo.size(); i++) { + assertEquals(fifo.size() - i, fifo.available(), "removing data"); + assertEquals(100.0 + i, fifo.read(), "reading back data"); + } + watchdog.interrupt(); + } + + /** + * Wrap around several times to test masking. + */ + @Test + public void testWrapping() { + + final int chunk = 5; + AudioFifo fifo = new AudioFifo(); + fifo.allocate(8); + double value = 1000.0; + for (int i = 0; i < (fifo.size() * chunk); i++) { + value = checkFifoChunk(fifo, value, chunk); + } + + } + + private double checkFifoChunk(AudioFifo fifo, double value, int chunk) { + for (int i = 0; i < chunk; i++) { + assertEquals(i, fifo.available(), "adding data"); + fifo.write(value + i); + } + for (int i = 0; i < chunk; i++) { + assertEquals(chunk - i, fifo.available(), "removing data"); + assertEquals(value + i, fifo.read(), "reading back data"); + } + return value + chunk; + } + + @Test + public void testBadSize() { + try { + AudioFifo fifo = new AudioFifo(); + fifo.allocate(20); // not power of 2 + fail("should not get here"); + } catch (IllegalArgumentException ignored) { + return; + } + + fail("should have caught size exception"); + } + + @Test + public void testSingleReadWait() { + final int chunk = 5; + final AudioFifo fifo = new AudioFifo(); + fifo.allocate(8); + + fifo.setWriteWaitEnabled(false); + fifo.setReadWaitEnabled(true); + final double value = 50.0; + + // Schedule a delayed write in another thread. + new Thread(() -> { + try { + Thread.sleep(200); + for (int i = 0; i < chunk; i++) { + fifo.write(value + i); + } + } catch (InterruptedException e) { + e.printStackTrace(); + } + }).start(); + + Thread watchdog = startWatchdog(500); + for (int i = 0; i < chunk; i++) { + assertEquals(value + i, fifo.read(), "reading back data"); + } + watchdog.interrupt(); + } + + private Thread startWatchdog(final int msec) { + Thread watchdog = new Thread(() -> { + try { + Thread.sleep(msec); + fail("test must still be waiting"); + } catch (InterruptedException ignored) { + } + }); + watchdog.start(); + return watchdog; + } + + @Test + public void testSingleWriteWait() { + final int chunk = 13; + final AudioFifo fifo = new AudioFifo(); + fifo.allocate(8); + + fifo.setWriteWaitEnabled(true); + fifo.setReadWaitEnabled(true); + final double value = 50.0; + + // Schedule a delayed read in another thread. + Thread readThread = new Thread(() -> { + try { + Thread.sleep(200); + for (int i = 0; i < chunk; i++) { + // LOGGER.debug( "testSingleWriteWait: try to read" ); + double got = fifo.read(); + assertEquals(value + i, got, "adding data"); + // LOGGER.debug( "testSingleWriteWait: read " + got ); + } + } catch (InterruptedException e) { + e.printStackTrace(); + } + }); + readThread.start(); + + Thread watchdog = startWatchdog(500); + // Try to write more than will fit so we will hang. + for (int i = 0; i < chunk; i++) { + fifo.write(value + i); + } + watchdog.interrupt(); + + try { + readThread.join(200); + } catch (InterruptedException e) { + e.printStackTrace(); + } + assertFalse(readThread.isAlive(), "readThread should be done."); + } + + @Test + public void testBlockReadWait() { + final int chunk = 50; + final AudioFifo fifo = new AudioFifo(); + fifo.allocate(8); + + fifo.setWriteWaitEnabled(false); + fifo.setReadWaitEnabled(true); + final double value = 300.0; + double[] readBuffer = new double[chunk]; + + // Schedule delayed writes in another thread. + new Thread(() -> { + int numWritten = 0; + double[] writeBuffer = new double[4]; + try { + while (numWritten < chunk) { + Thread.sleep(30); + for (int i = 0; i < writeBuffer.length; i++) { + writeBuffer[i] = value + numWritten; + numWritten += 1; + } + + fifo.write(writeBuffer); + } + } catch (InterruptedException e) { + e.printStackTrace(); + } + }).start(); + + Thread watchdog = startWatchdog(600); + fifo.read(readBuffer); + for (int i = 0; i < chunk; i++) { + assertEquals(value + i, readBuffer[i], "reading back data"); + } + watchdog.interrupt(); + + } + + @Test + public void testBlockReadAndWriteWaitStress() { + final int chunk = 10000000; // 10 Megabytes + final AudioFifo fifo = new AudioFifo(); + fifo.allocate(8); + + fifo.setWriteWaitEnabled(true); + fifo.setReadWaitEnabled(true); + final double value = 50.0; + + // Schedule a delayed write in another thread. + new Thread(() -> { + try { + Thread.sleep(200); + for (int i = 0; i < chunk; i++) { + fifo.write(value + i); + } + } catch (InterruptedException e) { + e.printStackTrace(); + } + }).start(); + + Thread watchdog = startWatchdog(10000); + for (int i = 0; i < chunk; i++) { + assertEquals(value + i, fifo.read(), "reading back data"); + } + watchdog.interrupt(); + } +} diff --git a/src/test/java/com/jsyn/engine/TestWaveFileReadWrite.java b/src/test/java/com/jsyn/engine/TestWaveFileReadWrite.java new file mode 100644 index 0000000..b5051e6 --- /dev/null +++ b/src/test/java/com/jsyn/engine/TestWaveFileReadWrite.java @@ -0,0 +1,114 @@ +/* + * 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.engine; + +import java.io.File; +import java.io.IOException; + +import com.jsyn.data.FloatSample; +import com.jsyn.util.SampleLoader; +import com.jsyn.util.WaveFileWriter; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + +public class TestWaveFileReadWrite { + + private static final Logger LOGGER = LoggerFactory.getLogger(TestWaveFileReadWrite.class); + + public void checkWriteReadWave(int numChannels, float[] data) throws IOException { + File temp = File.createTempFile("test_wave", ".wav"); + temp.deleteOnExit(); + LOGGER.debug("Creating wave file " + temp); + + WaveFileWriter writer = new WaveFileWriter(temp); + writer.setFrameRate(44100); + writer.setSamplesPerFrame(numChannels); + writer.setBitsPerSample(16); + + for (var datum : data) { + writer.write(datum); + } + writer.close(); + + // TODO Make sure blow up if writing after close. + // writer.write( 0.7 ); + + FloatSample sample = SampleLoader.loadFloatSample(temp); + assertEquals(numChannels, sample.getChannelsPerFrame(), "stereo"); + assertEquals(44100.0, sample.getFrameRate(), "frame rate"); + + for (int i = 0; i < data.length; i++) { + float v = data[i]; + if (v > 1.0) + v = 1.0f; + else if (v < -1.0) + v = -1.0f; + assertEquals(v, sample.readDouble(i), 0.0001, "sample data"); + } + + } + + @Test + public void testRamp() throws IOException { + float[] data = new float[200]; + for (int i = 0; i < data.length; i++) { + data[i] = i / 1000.0f; + } + + checkWriteReadWave(2, data); + } + + @Test + public void testClippedSine() throws IOException { + float[] data = new float[200]; + for (int i = 0; i < data.length; i++) { + double phase = i * Math.PI * 2.0 / 100; + data[i] = (float) (1.3 * Math.sin(phase)); + } + + checkWriteReadWave(2, data); + } + + @Test + public void testArguments() throws IOException { + File temp = File.createTempFile("test_wave", ".wav"); + temp.deleteOnExit(); + LOGGER.debug("Creating wave file " + temp); + + WaveFileWriter writer = new WaveFileWriter(temp); + writer.setBitsPerSample(16); + assertEquals(16, writer.getBitsPerSample(), "bitsPerSample"); + writer.setBitsPerSample(24); + assertEquals(24, writer.getBitsPerSample(), "bitsPerSample"); + try { + writer.setBitsPerSample(17); + fail("tried setting illegal value"); + } catch (IllegalArgumentException e) { + // e.printStackTrace(); + return; + } finally { + writer.close(); + } + + fail("17 generated exception"); + } + +} diff --git a/src/test/java/com/jsyn/midi/TestMidiLoop.java b/src/test/java/com/jsyn/midi/TestMidiLoop.java new file mode 100644 index 0000000..fa7ba2c --- /dev/null +++ b/src/test/java/com/jsyn/midi/TestMidiLoop.java @@ -0,0 +1,87 @@ +/* + * Copyright 2010 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.midi; + +import javax.sound.midi.MidiDevice; +import javax.sound.midi.MidiMessage; +import javax.sound.midi.MidiUnavailableException; +import javax.sound.midi.Receiver; + +import com.jsyn.devices.javasound.MidiDeviceTools; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Connect a USB MIDI Keyboard to the internal MIDI Synthesizer using JavaSound. + * + * @author Phil Burk (C) 2010 Mobileer Inc + */ +public class TestMidiLoop { + + private static final Logger LOGGER = LoggerFactory.getLogger(TestMidiLoop.class); + + @Test + private void midiLoop() { + try { + for (int result = 0, i = 0; i < 3 && result == 0; i++) { + result = test(); + } + } catch (MidiUnavailableException | InterruptedException e) { + e.printStackTrace(); + } + } + + // Write a Receiver to get the messages from a Transmitter. + static class CustomReceiver implements Receiver { + @Override + public void close() { + System.out.print("Receiver.close() was called."); + } + + @Override + public void send(MidiMessage message, long timeStamp) { + byte[] bytes = message.getMessage(); + LOGGER.debug("Got " + bytes.length + " bytes."); + } + } + + public int test() throws MidiUnavailableException, InterruptedException { + + int result = -1; + MidiDevice keyboard = MidiDeviceTools.findKeyboard(); + Receiver receiver = new CustomReceiver(); + // Just use default synthesizer. + if (keyboard != null) { + // If you forget to open them you will hear no sound. + keyboard.open(); + // Put the receiver in the transmitter. + // This gives fairly low latency playing. + keyboard.getTransmitter().setReceiver(receiver); + LOGGER.debug("Play MIDI keyboard: " + keyboard.getDeviceInfo().getDescription()); + result = 0; + Thread.sleep(4000); + LOGGER.debug("Close the keyboard. It may not work after this according to the docs!"); + keyboard.close(); + } else { + LOGGER.debug("Could not find a keyboard."); + } + return result; + } + + +} diff --git a/src/test/java/com/jsyn/ports/TestQueuedDataPort.java b/src/test/java/com/jsyn/ports/TestQueuedDataPort.java new file mode 100644 index 0000000..65c0127 --- /dev/null +++ b/src/test/java/com/jsyn/ports/TestQueuedDataPort.java @@ -0,0 +1,549 @@ +/* + * 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.JSyn; +import com.jsyn.Synthesizer; +import com.jsyn.data.FloatSample; +import com.jsyn.data.SequentialData; +import com.jsyn.data.ShortSample; +import com.jsyn.unitgen.FixedRateMonoReader; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Test sample and envelope queuing and looping. + * + * @author Phil Burk, (C) 2009 Mobileer Inc + */ +public class TestQueuedDataPort { + + private static final Logger LOGGER = LoggerFactory.getLogger(TestQueuedDataPort.class); + + private static Synthesizer synth; + private static final float[] floatData = { + 0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f + }; + private static FloatSample floatSample; + private static FixedRateMonoReader reader; + + @BeforeAll + private static void setUp() { + synth = JSyn.createSynthesizer(); + synth.setRealTime(false); + synth.start(); + } + + @AfterAll + private static void tearDown() { + synth.stop(); + } + + private void queueDirect(UnitDataQueuePort port, SequentialData data, int startFrame, + int numFrames) { + queueDirect(port, data, startFrame, numFrames, 0); + } + + private void queueDirect(UnitDataQueuePort port, SequentialData data, int startFrame, + int numFrames, int numLoops) { + QueueDataCommand command = port.createQueueDataCommand(data, startFrame, numFrames); + command.setNumLoops(numLoops); + port.addQueuedBlock(command); + } + + @Test + public void testQueueSingleShort() { + short[] data = { + 234, -9876, 4567 + }; + ShortSample sample = new ShortSample(data.length, 1); + sample.write(data); + + UnitDataQueuePort dataQueue = new UnitDataQueuePort("test"); + assertFalse(dataQueue.hasMore(), "start empty"); + + queueDirect(dataQueue, sample, 0, data.length); + checkQueuedData(data, dataQueue, 0, data.length); + + assertFalse(dataQueue.hasMore(), "end empty"); + } + + @Test + public void testQueueSingleFloat() { + float[] data = { + 0.4f, 1.9f, 22.7f + }; + FloatSample sample = new FloatSample(data.length, 1); + sample.write(data); + + UnitDataQueuePort dataQueue = new UnitDataQueuePort("test"); + assertFalse(dataQueue.hasMore(), "start empty"); + + queueDirect(dataQueue, sample, 0, data.length); + checkQueuedData(data, dataQueue, 0, data.length); + + assertFalse(dataQueue.hasMore(), "end empty"); + } + + @Test + public void testQueueOutOfBounds() { + float[] data = { + 0.4f, 1.9f, 22.7f + }; + FloatSample sample = new FloatSample(data.length, 1); + sample.write(data); + + UnitDataQueuePort dataQueue = new UnitDataQueuePort("test"); + boolean caught = false; + try { + queueDirect(dataQueue, sample, 0, sample.getNumFrames() + 1); // should cause an error! + } catch(IllegalArgumentException e) { + caught = true; + } + assertTrue(caught, "expect exception when we go past end of the array"); + + caught = false; + try { + queueDirect(dataQueue, sample, 1, sample.getNumFrames()); // should cause an error! + } catch(IllegalArgumentException e) { + caught = true; + } + assertTrue(caught, "expect exception when we go past end of the array"); + + caught = false; + try { + queueDirect(dataQueue, sample, -1, sample.getNumFrames()); // should cause an error! + } catch(IllegalArgumentException e) { + caught = true; + } + assertTrue(caught, "expect exception when we start before beginning of the array"); + } + + @Test + public void testQueueMultiple() { + short[] data = { + 234, 17777, -9876, 4567, -14287 + }; + ShortSample sample = new ShortSample(data.length, 1); + sample.write(data); + + UnitDataQueuePort dataQueue = new UnitDataQueuePort("test"); + assertFalse(dataQueue.hasMore(), "start empty"); + + queueDirect(dataQueue, sample, 1, 3); + queueDirect(dataQueue, sample, 0, 5); + queueDirect(dataQueue, sample, 2, 2); + + checkQueuedData(data, dataQueue, 1, 3); + checkQueuedData(data, dataQueue, 0, 5); + checkQueuedData(data, dataQueue, 2, 2); + + assertFalse(dataQueue.hasMore(), "end empty"); + } + + @Test + public void testQueueNoLoops() throws InterruptedException { + LOGGER.debug("testQueueNoLoops() ================"); + UnitDataQueuePort dataQueue = setupFloatSample(); + + dataQueue.queueOn(floatSample, synth.createTimeStamp()); + // Advance synth so that the queue command propagates to the engine. + synth.sleepUntil(synth.getCurrentTime() + 0.01); + + // play entire sample + checkQueuedData(floatData, dataQueue, 0, floatData.length); + + assertFalse(dataQueue.hasMore(), "end empty"); + } + + @Test + public void testQueueLoopForever() throws InterruptedException { + LOGGER.debug("testQueueLoopForever() ================"); + + UnitDataQueuePort dataQueue = setupFloatSample(); + + dataQueue.queue(floatSample, 0, 3); + dataQueue.queueLoop(floatSample, 3, 4); + + // Advance synth so that the queue commands propagate to the engine. + synth.sleepUntil(synth.getCurrentTime() + 0.01); + + checkQueuedData(floatData, dataQueue, 0, 3); + checkQueuedData(floatData, dataQueue, 3, 4); + checkQueuedData(floatData, dataQueue, 3, 4); + checkQueuedData(floatData, dataQueue, 3, 4); + checkQueuedData(floatData, dataQueue, 3, 1); + + // queue final release + dataQueue.queue(floatSample, 3, 5); + synth.sleepUntil(synth.getCurrentTime() + 0.01); + // current loop will finish + checkQueuedData(floatData, dataQueue, 4, 3); + // release portion will play + checkQueuedData(floatData, dataQueue, 3, 5); + + assertFalse(dataQueue.hasMore(), "end empty"); + } + + @Test + public void testQueueLoopAtLeastOnce() throws InterruptedException { + LOGGER.debug("testQueueLoopAtLeastOnce() ================"); + + UnitDataQueuePort dataQueue = setupFloatSample(); + + dataQueue.queue(floatSample, 0, 3); + dataQueue.queueLoop(floatSample, 3, 2); // this should play at least once + dataQueue.queue(floatSample, 5, 2); + + // Advance synth so that the queue commands propagate to the engine. + synth.sleepUntil(synth.getCurrentTime() + 0.01); + + checkQueuedData(floatData, dataQueue, 0, 3); + checkQueuedData(floatData, dataQueue, 3, 2); + checkQueuedData(floatData, dataQueue, 5, 2); + + assertFalse(dataQueue.hasMore(), "end empty"); + } + + @Test + public void testQueueNumLoops() throws InterruptedException { + LOGGER.debug("testQueueNumLoops() ================"); + UnitDataQueuePort dataQueue = setupFloatSample(); + + dataQueue.queue(floatSample, 0, 2); + + int numLoopsA = 5; + dataQueue.queueLoop(floatSample, 2, 3, numLoopsA); + + dataQueue.queue(floatSample, 4, 2); + + int numLoopsB = 3; + dataQueue.queueLoop(floatSample, 3, 4, numLoopsB); + + dataQueue.queue(floatSample, 5, 2); + + // Advance synth so that the queue commands propagate to the engine. + synth.sleepUntil(synth.getCurrentTime() + 0.01); + + checkQueuedData(floatData, dataQueue, 0, 2); + for (int i = 0; i < (numLoopsA + 1); i++) { + LOGGER.debug("loop A #" + i); + checkQueuedData(floatData, dataQueue, 2, 3); + } + checkQueuedData(floatData, dataQueue, 4, 2); + for (int i = 0; i < (numLoopsB + 1); i++) { + LOGGER.debug("loop B #" + i); + checkQueuedData(floatData, dataQueue, 3, 4); + } + + checkQueuedData(floatData, dataQueue, 5, 2); + + assertFalse(dataQueue.hasMore(), "end empty"); + } + + private UnitDataQueuePort setupFloatSample() { + floatSample = new FloatSample(floatData.length, 1); + floatSample.write(floatData); + + synth.add(reader = new FixedRateMonoReader()); + UnitDataQueuePort dataQueue = reader.dataQueue; + assertFalse(dataQueue.hasMore(), "start empty"); + return dataQueue; + } + + @Test + public void testQueueSustainLoop() throws InterruptedException { + LOGGER.debug("testQueueSustainLoop() ================"); + + UnitDataQueuePort dataQueue = setupFloatSample(); + + // set up sustain loops =========================== + floatSample.setSustainBegin(2); + floatSample.setSustainEnd(4); + floatSample.setReleaseBegin(-1); + floatSample.setReleaseEnd(-1); + + dataQueue.queueOn(floatSample, synth.createTimeStamp()); + // Advance synth so that the queue command propagates to the engine. + synth.sleepUntil(synth.getCurrentTime() + 0.01); + + checkQueuedData(floatData, dataQueue, 0, 2); + checkQueuedData(floatData, dataQueue, 2, 2); + checkQueuedData(floatData, dataQueue, 2, 2); + checkQueuedData(floatData, dataQueue, 2, 1); // looping + + dataQueue.queueOff(floatSample, true); // queue off in middle of loop + synth.sleepUntil(synth.getCurrentTime() + 0.01); + + checkQueuedData(floatData, dataQueue, 3, 5); // release + assertFalse(dataQueue.hasMore(), "end empty"); + } + + @Test + public void testQueueReleaseLoop() throws InterruptedException { + LOGGER.debug("testQueueReleaseLoop() ================"); + UnitDataQueuePort dataQueue = setupFloatSample(); + + // set up sustain loops =========================== + floatSample.setSustainBegin(-1); + floatSample.setSustainEnd(-1); + floatSample.setReleaseBegin(4); + floatSample.setReleaseEnd(6); + + dataQueue.queueOn(floatSample, synth.createTimeStamp()); + // Advance synth so that the queue command propagates to the engine. + synth.sleepUntil(synth.getCurrentTime() + 0.01); + + checkQueuedData(floatData, dataQueue, 0, 4); + checkQueuedData(floatData, dataQueue, 4, 2); + checkQueuedData(floatData, dataQueue, 4, 2); + checkQueuedData(floatData, dataQueue, 4, 2); // looping in release cuz no + // sustain loop + + dataQueue.queueOff(floatSample, true); // queue off in middle of loop + synth.sleepUntil(synth.getCurrentTime() + 0.01); + + checkQueuedData(floatData, dataQueue, 4, 2); + checkQueuedData(floatData, dataQueue, 4, 2); // still looping + assertTrue(dataQueue.hasMore(), "end full"); + } + + @Test + public void testQueueSustainReleaseLoops() throws InterruptedException { + LOGGER.debug("testQueueSustainReleaseLoops() ================"); + UnitDataQueuePort dataQueue = setupFloatSample(); + + // set up sustain loops =========================== + floatSample.setSustainBegin(2); + floatSample.setSustainEnd(4); + floatSample.setReleaseBegin(5); + floatSample.setReleaseEnd(7); + + dataQueue.queueOn(floatSample, synth.createTimeStamp()); + // Advance synth so that the queue command propagates to the engine. + synth.sleepUntil(synth.getCurrentTime() + 0.01); + + checkQueuedData(floatData, dataQueue, 0, 4); + checkQueuedData(floatData, dataQueue, 2, 2); + checkQueuedData(floatData, dataQueue, 2, 1); // middle of sustain loop + + dataQueue.queueOff(floatSample, true); // queue off in middle of loop + synth.sleepUntil(synth.getCurrentTime() + 0.01); + + checkQueuedData(floatData, dataQueue, 3, 2); + checkQueuedData(floatData, dataQueue, 5, 2); // release loop + checkQueuedData(floatData, dataQueue, 5, 2); // release loop + assertTrue(dataQueue.hasMore(), "end full"); + } + + @Test + private void checkQueuedData(short[] data, UnitDataQueuePort dataQueue, int offset, + int numFrames) { + for (int i = 0; i < numFrames; i++) { + assertTrue(dataQueue.hasMore(), "got data"); + double value = dataQueue.readNextMonoDouble(synth.getFramePeriod()); + assertEquals(data[i + offset] / 32768.0, value, 0.0001, "data matches"); + } + } + + private void checkQueuedData(float[] data, UnitDataQueuePort dataQueue, int offset, + int numFrames) { + for (int i = 0; i < numFrames; i++) { + assertTrue(dataQueue.hasMore(), "got data"); + double value = dataQueue.readNextMonoDouble(synth.getFramePeriod()); + assertEquals(data[i + offset], value, 0.0001, "data matches"); + } + } + + static class TestQueueCallback implements UnitDataQueueCallback { + boolean gotStarted = false; + boolean gotLooped = false; + boolean gotFinished = false; + QueueDataEvent lastEvent; + + @Override + public void started(QueueDataEvent event) { + LOGGER.debug("Callback started."); + gotStarted = true; + lastEvent = event; + } + + @Override + public void looped(QueueDataEvent event) { + LOGGER.debug("Callback looped."); + gotLooped = true; + lastEvent = event; + } + + @Override + public void finished(QueueDataEvent event) { + LOGGER.debug("Callback finished."); + gotFinished = true; + lastEvent = event; + } + } + + @Test + public void testQueueCallback() { + float[] data = { + 0.2f, -8.9f, 2.7f + }; + FloatSample sample = new FloatSample(data.length, 1); + sample.write(data); + + UnitDataQueuePort dataQueue = new UnitDataQueuePort("test"); + assertFalse(dataQueue.hasMore(), "start empty"); + + // Create an object to be called when the queued data is done. + TestQueueCallback callback = new TestQueueCallback(); + + QueueDataCommand command = dataQueue.createQueueDataCommand(sample, 0, data.length); + command.setCallback(callback); + command.setNumLoops(2); + dataQueue.addQueuedBlock(command); + + // Check to see if flags get set true by callback. + dataQueue.firePendingCallbacks(); + assertFalse(callback.gotStarted, "not started yet"); + assertFalse(callback.gotLooped, "not looped yet"); + assertFalse(callback.gotFinished, "not finished yet"); + + checkQueuedData(data, dataQueue, 0, 1); + dataQueue.firePendingCallbacks(); + assertTrue(callback.gotStarted, "should be started now"); + assertFalse(callback.gotLooped, "not looped yet"); + assertFalse(callback.gotFinished, "not finished yet"); + assertEquals(dataQueue, callback.lastEvent.getSource(), "check source of event"); + assertEquals(sample, callback.lastEvent.getSequentialData(), "check sample"); + assertEquals(2, callback.lastEvent.getLoopsLeft(), "check loopCount"); + + checkQueuedData(data, dataQueue, 1, data.length - 1); + dataQueue.firePendingCallbacks(); + assertTrue(callback.gotLooped, "should be looped now"); + assertEquals(1, callback.lastEvent.getLoopsLeft(), "check loopCount"); + assertFalse(callback.gotFinished, "not finished yet"); + + checkQueuedData(data, dataQueue, 0, data.length); + dataQueue.firePendingCallbacks(); + assertEquals(0, callback.lastEvent.getLoopsLeft(), "check loopCount"); + + checkQueuedData(data, dataQueue, 0, data.length); + dataQueue.firePendingCallbacks(); + assertTrue(callback.gotFinished, "should be finished now"); + + assertFalse(dataQueue.hasMore(), "end empty"); + } + + @Test + public void testImmediate() { + float[] data = { + 0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f, 10.0f, 11.0f + }; + FloatSample sample = new FloatSample(data.length, 1); + sample.write(data); + + UnitDataQueuePort dataQueue = new UnitDataQueuePort("test"); + dataQueue.queue(sample); + + // Only play some of the data then interrupt it with an immediate block. + checkQueuedData(data, dataQueue, 0, 3); + + QueueDataCommand command = dataQueue.createQueueDataCommand(sample, 7, 3); + command.setImmediate(true); + command.run(); // execute "immediate" operation and add to block list + + // Should already be in new data. + checkQueuedData(data, dataQueue, 7, 3); + } + + @Test + public void testCrossFade() { + float[] data1 = { + 0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f + }; + float[] data2 = { + 20.0f, 19.0f, 18.0f, 17.0f, 16.0f, 15.0f, 14.0f, 13.0f, 12.0f, 11.0f + }; + FloatSample sample1 = new FloatSample(data1); + FloatSample sample2 = new FloatSample(data2); + + UnitDataQueuePort dataQueue = new UnitDataQueuePort("test"); + dataQueue.queue(sample1, 0, 4); + + QueueDataCommand command = dataQueue.createQueueDataCommand(sample2, 1, 8); + command.setCrossFadeIn(3); + command.run(); // execute "immediate" operation and add to block list + + // Only play some of the data then crossfade to another sample. + checkQueuedData(data1, dataQueue, 0, 4); + + for (int i = 0; i < 3; i++) { + double factor = i / 3.0; + double value = ((1.0 - factor) * data1[i + 4]) + (factor * data2[i + 1]); + LOGGER.debug("i = " + i + ", factor = " + factor + ", value = " + value); + + double actual = dataQueue.readNextMonoDouble(synth.getFramePeriod()); + assertEquals(value, actual, 0.00001, "crossfade " + i); + } + + // Should already be in new data. + checkQueuedData(data2, dataQueue, 4, 5); + } + + @Test + public void testImmediateCrossFade() { + float[] data1 = { + 0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f + }; + float[] data2 = { + 20.0f, 19.0f, 18.0f, 17.0f, 16.0f, 15.0f, 14.0f, 13.0f, 12.0f, 11.0f + }; + FloatSample sample1 = new FloatSample(data1); + FloatSample sample2 = new FloatSample(data2); + + UnitDataQueuePort dataQueue = new UnitDataQueuePort("test"); + dataQueue.queue(sample1, 0, 4); + + // Only play some of the data then crossfade to another sample. + int beforeInterrupt = 2; + checkQueuedData(data1, dataQueue, 0, beforeInterrupt); + + QueueDataCommand command = dataQueue.createQueueDataCommand(sample2, 1, 8); + command.setImmediate(true); + command.setCrossFadeIn(3); + command.run(); // execute "immediate" operation and add to block list + + for (int i = 0; i < 3; i++) { + double factor = i / 3.0; + double value = ((1.0 - factor) * data1[i + beforeInterrupt]) + (factor * data2[i + 1]); + LOGGER.debug("i = " + i + ", factor = " + factor + ", value = " + value); + + double actual = dataQueue.readNextMonoDouble(synth.getFramePeriod()); + assertEquals(value, actual, 0.00001, "crossfade " + i); + } + + // Should already be in new data. + checkQueuedData(data2, dataQueue, 4, 5); + } +} diff --git a/src/test/java/com/jsyn/ports/TestSequentialData.java b/src/test/java/com/jsyn/ports/TestSequentialData.java new file mode 100644 index 0000000..2f27ec2 --- /dev/null +++ b/src/test/java/com/jsyn/ports/TestSequentialData.java @@ -0,0 +1,55 @@ +/* + * 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.FloatSample; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class TestSequentialData { + + private static final Logger LOGGER = LoggerFactory.getLogger(TestSequentialData.class); + + private final static float[] data1 = { + 0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f + }; + + private final static float[] data2 = { + 20.0f, 19.0f, 18.0f, 17.0f, 16.0f, 15.0f, 14.0f, 13.0f, 12.0f, 11.0f + }; + + @Test + public void testCrossfade() { + var sample1 = new FloatSample(data1); + var sample2 = new FloatSample(data2); + SequentialDataCrossfade xfade = new SequentialDataCrossfade(); + xfade.setup(sample1, 4, 3, sample2, 1, 6); + + for (int i = 0; i < 3; i++) { + double factor = i / 3.0; + double value = ((1.0 - factor) * data1[i + 4]) + (factor * data2[i + 1]); + LOGGER.debug("i = " + i + ", factor = " + factor + ", value = " + value); + assertEquals(value, xfade.readDouble(i), 0.00001, "crossfade " + i); + } + for (int i = 3; i < 6; i++) { + assertEquals(sample2.readDouble(i + 1), xfade.readDouble(i), 0.00001, "crossfade " + i); + } + } +} diff --git a/src/test/java/com/jsyn/ports/TestSet.java b/src/test/java/com/jsyn/ports/TestSet.java new file mode 100644 index 0000000..be426b7 --- /dev/null +++ b/src/test/java/com/jsyn/ports/TestSet.java @@ -0,0 +1,89 @@ +/* + * 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.engine.SynthesisEngine; +import com.jsyn.unitgen.Minimum; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + +public class TestSet { + + /** Internal value setting. */ + @Test + public void testSetValue() { + int numParts = 4; + UnitInputPort port = new UnitInputPort(numParts, "Tester"); + port.setValueInternal(0, 100.0); + port.setValueInternal(2, 120.0); + port.setValueInternal(1, 110.0); + port.setValueInternal(3, 130.0); + assertEquals(100.0, port.getValue(0), "check port value"); + assertEquals(120.0, port.getValue(2), "check port value"); + assertEquals(110.0, port.getValue(1), "check port value"); + assertEquals(130.0, port.getValue(3), "check port value"); + } + + @Test + public void testSet() throws InterruptedException { + SynthesisEngine synthesisEngine = new SynthesisEngine(); + synthesisEngine.setRealTime(false); + synthesisEngine.start(); + synthesisEngine.sleepUntil(0.01); + Minimum min; + synthesisEngine.add(min = new Minimum()); + + double x = 33.99; + double y = 8.31; + min.inputA.set(x); + min.inputB.set(y); + synthesisEngine.sleepFor(0.01); + assertEquals(x, min.inputA.getValue(), "min set A"); + assertEquals(y, min.inputB.getValue(), "min set B"); + min.start(); + synthesisEngine.sleepFor(0.01); + + assertEquals(y, min.output.getValue(), "min output"); + synthesisEngine.stop(); + } + + /** if we use a port index out of range we want to know now and not blow up the engine. */ + @Test + public void testSetBadPort() throws InterruptedException { + SynthesisEngine synthesisEngine = new SynthesisEngine(); + synthesisEngine.setRealTime(false); + synthesisEngine.start(); + Minimum min; + synthesisEngine.add(min = new Minimum()); + + min.start(); + try { + min.inputA.set(1, 23.45); + } catch (ArrayIndexOutOfBoundsException ignored) { + } catch (Exception e) { + fail("Catch port out of range, caught " + e); + } + + // Don't blow up here. + synthesisEngine.sleepUntil(0.01); + + synthesisEngine.stop(); + } + +} diff --git a/src/test/java/com/jsyn/research/BenchMultiThreading.java b/src/test/java/com/jsyn/research/BenchMultiThreading.java new file mode 100644 index 0000000..24624c5 --- /dev/null +++ b/src/test/java/com/jsyn/research/BenchMultiThreading.java @@ -0,0 +1,152 @@ +/* + * 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.research; + +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +// TODO: Use thread pools, or maybe JMH? +public class BenchMultiThreading { + + private static final Logger LOGGER = LoggerFactory.getLogger(BenchMultiThreading.class); + + private static final int FRAMES_PER_BLOCK = 64; + int numThreads = 4; + int numLoops = 100000; + private ArrayList threadList; + + static class CustomThread extends Thread { + long frameCount = 0; + long desiredFrame = 0; + Object semaphore = new Object(); + Object goSemaphore = new Object(); + volatile boolean go = true; + long startNano; + long stopNano; + long maxElapsed; + + @Override + public void run() { + try { + startNano = System.nanoTime(); + while (go) { + // Watch for long delays. + stopNano = System.nanoTime(); + long elapsed = stopNano - startNano; + startNano = System.nanoTime(); + if (elapsed > maxElapsed) { + maxElapsed = elapsed; + } + + synchronized (semaphore) { + // Audio synthesis would occur here. + frameCount += 1; + // LOGGER.debug( this + " generating frame " + + // frameCount ); + semaphore.notify(); + } + synchronized (goSemaphore) { + while (desiredFrame <= frameCount) { + goSemaphore.wait(); + } + } + long stopNano = System.nanoTime(); + } + } catch (InterruptedException e) { + LOGGER.debug("CustomThread interrupted. "); + } + LOGGER.debug("Finishing " + this); + } + + public void abort() { + go = false; + interrupt(); + } + + public void waitForFrame(long targetFrame) throws InterruptedException { + synchronized (semaphore) { + while (frameCount < targetFrame) { + semaphore.wait(); + } + } + } + + public void generateFrame(long desiredFrame) { + synchronized (goSemaphore) { + this.desiredFrame = desiredFrame; + goSemaphore.notify(); + } + } + + } + + @Test + public void testMultiThreads() { + threadList = new ArrayList<>(); + for (int i = 0; i < numThreads; i++) { + CustomThread thread = new CustomThread(); + threadList.add(thread); + thread.start(); + } + + long frameCount = 0; + long startTime = System.currentTimeMillis(); + try { + for (int i = 0; i < numLoops; i++) { + frameCount += 1; + waitForThreads(frameCount); + // LOGGER.debug("got frame " + frameCount ); + } + } catch (InterruptedException e) { + e.printStackTrace(); + } + long stopTime = System.currentTimeMillis(); + long elapsedTime = stopTime - startTime; + double elapsedSeconds = 0.001 * elapsedTime; + double blocksPerSecond = numLoops / elapsedSeconds; + System.out.format("blocksPerSecond = %10.3f\n", blocksPerSecond); + double framesPerSecond = blocksPerSecond * FRAMES_PER_BLOCK; + System.out.format("audio framesPerSecond = %10.3f at %d frames per block\n", + framesPerSecond, FRAMES_PER_BLOCK); + + for (CustomThread thread : threadList) { + System.out.format("max elapsed time is %d nanos or %f msec\n", thread.maxElapsed, + (thread.maxElapsed / 1000000.0)); + } + for (CustomThread thread : threadList) { + assertEquals(frameCount, thread.frameCount, "BlockCount must match "); + thread.abort(); + } + + } + + private void waitForThreads(long frameCount) throws InterruptedException { + for (CustomThread thread : threadList) { + // Ask threads to wake up and generate up to this frame. + thread.generateFrame(frameCount); + } + for (CustomThread thread : threadList) { + // Wait for all the threads to catch up. + thread.waitForFrame(frameCount); + } + } +} diff --git a/src/test/java/com/jsyn/research/RecordVariousRamps.java b/src/test/java/com/jsyn/research/RecordVariousRamps.java new file mode 100644 index 0000000..7abb2b1 --- /dev/null +++ b/src/test/java/com/jsyn/research/RecordVariousRamps.java @@ -0,0 +1,193 @@ +/* + * Copyright 2014 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. + */ +/** + * Generate steps, linear ramps and smooth ramps. + * + * @author (C) 2014 Phil Burk + */ + +package com.jsyn.research; + +import java.io.File; +import java.io.IOException; + +import com.jsyn.JSyn; +import com.jsyn.Synthesizer; +import com.jsyn.unitgen.ContinuousRamp; +import com.jsyn.unitgen.LineOut; +import com.jsyn.unitgen.LinearRamp; +import com.jsyn.unitgen.Multiply; +import com.jsyn.unitgen.PassThrough; +import com.jsyn.unitgen.PowerOfTwo; +import com.jsyn.unitgen.SawtoothOscillatorBL; +import com.jsyn.unitgen.UnitFilter; +import com.jsyn.unitgen.UnitOscillator; +import com.jsyn.util.WaveRecorder; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class RecordVariousRamps { + + private static final Logger LOGGER = LoggerFactory.getLogger(RecordVariousRamps.class); + + private Synthesizer synth; + private UnitOscillator osc; + private Multiply multiplier; + private UnitFilter ramp; + private LinearRamp linearRamp; + private ContinuousRamp continuousRamp; + private LineOut lineOut; + private WaveRecorder recorder; + private PowerOfTwo powerOfTwo; + private static final int MODE_STEP = 0; + private static final int MODE_LINEAR = 1; + private static final int MODE_SMOOTH = 2; + private static final String[] modeNames = { + "step", "linear", "smooth" + }; + + private static final RampEvent[] rampData = { + new RampEvent(1.0, 1.5, 2.0), new RampEvent(-0.9, 0.5, 1.0), + new RampEvent(0.9, 0.5, 0.8), new RampEvent(-0.3, 0.5, 0.8), + new RampEvent(0.9, 0.5, 0.3), new RampEvent(-0.5, 0.5, 0.3), + new RampEvent(0.8, 2.0, 1.0), + }; + + private static class RampEvent { + double target; + double eventDuration; + double rampDuration; + + RampEvent(double target, double eventDuration, double rampDuration) { + this.target = target; + this.eventDuration = eventDuration; + this.rampDuration = rampDuration; + } + } + + private void test(int mode) throws IOException { + // Create a context for the synthesizer. + synth = JSyn.createSynthesizer(); + synth.setRealTime(false); + + File waveFile = new File("ramp_pitch_" + modeNames[mode] + ".wav"); + // Mono 16 bits. + recorder = new WaveRecorder(synth, waveFile, 1, 16); + LOGGER.debug("Writing to 16-bit WAV file " + waveFile.getAbsolutePath()); + + // Add some tone generators. + synth.add(osc = new SawtoothOscillatorBL()); + + // Add a controller that will sweep up. + synth.add(multiplier = new Multiply()); + synth.add(powerOfTwo = new PowerOfTwo()); + // Add an output unit. + synth.add(lineOut = new LineOut()); + multiplier.inputB.set(660.0); + + switch (mode) { + case MODE_STEP: + synth.add(ramp = new PassThrough()); + break; + case MODE_LINEAR: + synth.add(ramp = linearRamp = new LinearRamp()); + linearRamp.current.set(-1.0); + linearRamp.time.set(10.0); + break; + case MODE_SMOOTH: + synth.add(ramp = continuousRamp = new ContinuousRamp()); + continuousRamp.current.set(-1.0); + continuousRamp.time.set(10.0); + break; + } + + ramp.getInput().set(-1.0); + ramp.getOutput().connect(powerOfTwo.input); + + powerOfTwo.output.connect(multiplier.inputA); + multiplier.output.connect(osc.frequency); + + // Connect the oscillator to the left and right audio output. + osc.output.connect(0, lineOut.input, 0); + osc.output.connect(0, lineOut.input, 1); + + // Start synthesizer using default stereo output at 44100 Hz. + synth.start(); + + osc.output.connect(0, recorder.getInput(), 0); + // When we start the recorder it will pull data from the oscillator + // and sweeper. + recorder.start(); + + // We also need to start the LineOut if we want to hear it now. + lineOut.start(); + + // Get synthesizer time in seconds. + double nextEventTime = synth.getCurrentTime() + 1.0; + try { + synth.sleepUntil(nextEventTime); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + for (RampEvent rampEvent : rampData) { + + switch (mode) { + case MODE_STEP: + break; + case MODE_LINEAR: + linearRamp.time.set(rampEvent.rampDuration); + break; + case MODE_SMOOTH: + continuousRamp.time.set(rampEvent.rampDuration); + break; + } + ramp.getInput().set(rampEvent.target); + + nextEventTime += rampEvent.eventDuration; + LOGGER.debug("target = " + rampEvent.target + ", rampDur = " + + rampEvent.rampDuration + ", eventDur = " + rampEvent.eventDuration); + try { + synth.sleepUntil(nextEventTime); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + if (recorder != null) { + recorder.stop(); + recorder.close(); + } + // Stop everything. + synth.stop(); + } + + @Test + private void stepMode() throws IOException { + test(MODE_STEP); + } + + @Test + public void linearMode() throws IOException { + test(MODE_LINEAR); + } + + @Test + public void smoothMode() throws IOException { + test(MODE_SMOOTH); + } +} diff --git a/src/test/java/com/jsyn/swing/TestRangeModels.java b/src/test/java/com/jsyn/swing/TestRangeModels.java new file mode 100644 index 0000000..5d12601 --- /dev/null +++ b/src/test/java/com/jsyn/swing/TestRangeModels.java @@ -0,0 +1,53 @@ +/* + * 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.swing; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class TestRangeModels { + + public void checkDoubleRange(double dmin, double dmax, double dval) { + int resolution = 1000; + DoubleBoundedRangeModel model = new DoubleBoundedRangeModel("test", resolution, dmin, dmax, + dval); + assertEquals(dmin, model.getDoubleMinimum(), 0.0001, "setup min"); + assertEquals(dmax, model.getDoubleMaximum(), 0.0001, "setup max"); + assertEquals(dval, model.getDoubleValue(), 0.0001, "setup value"); + + model.setDoubleValue(dmin); + assertEquals(dmin, model.getDoubleValue(), 0.0001, "min double value"); + assertEquals(0, model.getValue(), "min value"); + + double dmid = (dmax + dmin) / 2.0; + model.setDoubleValue(dmid); + assertEquals(dmid, model.getDoubleValue(), 0.0001, "middle double value"); + assertEquals(resolution / 2, model.getValue(), "middle value"); + + model.setDoubleValue(dmax); + assertEquals(dmax, model.getDoubleValue(), 0.0001, "max double value"); + assertEquals(resolution, model.getValue(), "max value"); + + } + + @Test + public void testDoubleRange() { + checkDoubleRange(10.0, 20.0, 12.0); + checkDoubleRange(-1.0, 1.0, 0.5); + } +} diff --git a/src/test/java/com/jsyn/unitgen/CalibrateMoogFilter.java b/src/test/java/com/jsyn/unitgen/CalibrateMoogFilter.java new file mode 100644 index 0000000..1e74aa8 --- /dev/null +++ b/src/test/java/com/jsyn/unitgen/CalibrateMoogFilter.java @@ -0,0 +1,141 @@ +/* + * Copyright 2010 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.unitgen; + +import javax.swing.JApplet; + +import com.jsyn.JSyn; +import com.jsyn.Synthesizer; + +/** + * Play a sawtooth through a 4-pole filter. + * + * @author Phil Burk (C) 2010 Mobileer Inc + */ +public class CalibrateMoogFilter extends JApplet { + private Synthesizer synth; + private UnitOscillator oscillator; + private SineOscillator reference; + ZeroCrossingCounter zeroCounter; + PitchDetector pitchDetector; + ZeroCrossingCounter sineZeroCounter; + PitchDetector sinePitchDetector; + private FilterFourPoles filterMoog; + private LineOut lineOut; + + @Override + public void init() { + synth = JSyn.createSynthesizer(); + synth.setRealTime(false); + synth.add(oscillator = new SawtoothOscillatorBL()); + synth.add(reference = new SineOscillator()); + synth.add(filterMoog = new FilterFourPoles()); + synth.add(pitchDetector = new PitchDetector()); + synth.add(sinePitchDetector = new PitchDetector()); + synth.add(zeroCounter = new ZeroCrossingCounter()); + synth.add(sineZeroCounter = new ZeroCrossingCounter()); + synth.add(lineOut = new LineOut()); + + oscillator.output.connect(filterMoog.input); + filterMoog.output.connect(zeroCounter.input); + zeroCounter.output.connect(pitchDetector.input); + reference.output.connect(0, lineOut.input, 0); + filterMoog.output.connect(0, lineOut.input, 1); + + reference.output.connect(sineZeroCounter.input); + sineZeroCounter.output.connect(sinePitchDetector.input); + + oscillator.frequency.set(130.0); + oscillator.amplitude.set(0.001); + filterMoog.frequency.set(440.0); + filterMoog.Q.set(4.1); + } + + @Override + public void start() { + // Start synthesizer using default stereo output at 44100 Hz. + synth.start(); + pitchDetector.start(); + sinePitchDetector.start(); + lineOut.start(); + } + + @Override + public void stop() { + pitchDetector.stop(); + sinePitchDetector.stop(); + lineOut.stop(); + synth.stop(); + } + + public void test() { + init(); + start(); + try { + calibrate(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + stop(); + } + + private void calibrate() throws InterruptedException { + synth.sleepFor(2.0); + double freq = 100.0; + System.out + .printf("freq, moogFreq, ratio, moogConf, sineFreq, sineConf, moogZRate, sineZRate\n"); + long startingFrameCount = synth.getFrameCount(); + long startingMoogZeroCount = zeroCounter.getCount(); + long startingSineZeroCount = sineZeroCounter.getCount(); + for (int i = 0; i < 50; i++) { + reference.frequency.set(freq); + filterMoog.frequency.set(freq); + synth.sleepFor(2.0); + + long endingFrameCount = synth.getFrameCount(); + long elapsedFrames = endingFrameCount - startingFrameCount; + startingFrameCount = endingFrameCount; + + long endingMoogZeroCount = zeroCounter.getCount(); + long elapsedMoogZeros = endingMoogZeroCount - startingMoogZeroCount; + startingMoogZeroCount = endingMoogZeroCount; + + long endingSineZeroCount = sineZeroCounter.getCount(); + long elapsedSineZeros = endingSineZeroCount - startingSineZeroCount; + startingSineZeroCount = endingSineZeroCount; + + double moogZeroRate = elapsedMoogZeros * (double) synth.getFrameRate() / elapsedFrames; + double sineZeroRate = elapsedSineZeros * (double) synth.getFrameRate() / elapsedFrames; + + double moogMeasuredFreq = pitchDetector.frequency.get(); + double moogConfidence = pitchDetector.confidence.get(); + double sineMeasuredFreq = sinePitchDetector.frequency.get(); + double sineConfidence = sinePitchDetector.confidence.get(); + double ratio = freq / moogMeasuredFreq; + System.out.printf("%7.2f, %8.5f, %7.5f, %4.2f, %8.5f, %4.2f, %8.4f, %8.4f\n", freq, + moogMeasuredFreq, ratio, moogConfidence, sineMeasuredFreq, sineConfidence, + moogZeroRate, sineZeroRate); + + freq *= 1.1; + } + } + + public static void main(String[] args) { + new CalibrateMoogFilter().test(); + } + +} diff --git a/src/test/java/com/jsyn/unitgen/EnablingGate.java b/src/test/java/com/jsyn/unitgen/EnablingGate.java new file mode 100644 index 0000000..daf36be --- /dev/null +++ b/src/test/java/com/jsyn/unitgen/EnablingGate.java @@ -0,0 +1,51 @@ +/* + * Copyright 2010 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.unitgen; + +import com.jsyn.ports.UnitInputPort; + +/** + * This can be used to block the execution of upstream units. It can be placed at the output of a + * circuit and driven with an amplitude envelope. + * + * @author Phil Burk (C) 2010 Mobileer Inc + */ +public class EnablingGate extends UnitFilter { + public UnitInputPort gate; + + /* Define Unit Ports used by connect() and set(). */ + public EnablingGate() { + super(); + addPort(gate = new UnitInputPort("Gate")); + } + + @Override + public void generate(int start, int limit) { + double[] aValues = input.getValues(); + double[] bValues = gate.getValues(); + double[] outputs = output.getValues(); + for (int i = start; i < limit; i++) { + outputs[i] = aValues[i] * bValues[i]; + } + // If we end up at zero then disable pulling of data. + // We do this at the end so that envelope can get started. + if (outputs[limit - 1] <= 0.0) { + setEnabled(false); + } + } + +} diff --git a/src/test/java/com/jsyn/unitgen/NonRealTimeTestCase.java b/src/test/java/com/jsyn/unitgen/NonRealTimeTestCase.java new file mode 100644 index 0000000..bec5762 --- /dev/null +++ b/src/test/java/com/jsyn/unitgen/NonRealTimeTestCase.java @@ -0,0 +1,42 @@ +/* + * 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.unitgen; + +import com.jsyn.engine.SynthesisEngine; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; + +public abstract class NonRealTimeTestCase { + + protected SynthesisEngine synthesisEngine; + + public NonRealTimeTestCase() { + super(); + } + + @BeforeEach + private void setUp() { + synthesisEngine = new SynthesisEngine(); + synthesisEngine.setRealTime(false); + } + + @AfterEach + private void tearDown() { + synthesisEngine.stop(); + } + +} diff --git a/src/test/java/com/jsyn/unitgen/RecordMoogFilter.java b/src/test/java/com/jsyn/unitgen/RecordMoogFilter.java new file mode 100644 index 0000000..31a86be --- /dev/null +++ b/src/test/java/com/jsyn/unitgen/RecordMoogFilter.java @@ -0,0 +1,158 @@ +/* + * Copyright 2010 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.unitgen; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; + +import javax.swing.JApplet; + +import com.jsyn.JSyn; +import com.jsyn.Synthesizer; +import com.jsyn.util.WaveRecorder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Measure actual frequency as a function of input frequency and Q. + * + * @author Phil Burk (C) 2010 Mobileer Inc + */ +public class RecordMoogFilter extends JApplet { + + private static final Logger LOGGER = LoggerFactory.getLogger(RecordMoogFilter.class); + + private final static boolean SWEEP_Q = false; + private final static boolean SWEEP_FREQUENCY = true; + private final static int NUM_STEPS = 11; + + private final static double MIN_Q = 0.0; + private final static double DEFAULT_Q = 9.0; + private final static double MAX_Q = 10.0; + + private final static double MIN_FREQUENCY = 100.0; + private final static double DEFAULT_FREQUENCY = 500.0; + private final static double MAX_FREQUENCY = 4000.0; + + private Synthesizer synth; + private WhiteNoise source; + private SineOscillator reference; + private FilterFourPoles filterMoog; + private LineOut lineOut; + private WaveRecorder recorder; + + @Override + public void init() { + synth = JSyn.createSynthesizer(); + synth.setRealTime(false); + synth.add(source = new WhiteNoise()); + synth.add(filterMoog = new FilterFourPoles()); + synth.add(reference = new SineOscillator()); + synth.add(lineOut = new LineOut()); + + source.output.connect(filterMoog.input); + reference.output.connect(0, lineOut.input, 0); + filterMoog.output.connect(0, lineOut.input, 1); + + reference.amplitude.set(0.5); + source.amplitude.set(0.5); + filterMoog.frequency.set(DEFAULT_FREQUENCY); + filterMoog.Q.set(DEFAULT_Q); + + File waveFile = new File("temp_recording.wav"); + // Default is stereo, 16 bits. + try { + recorder = new WaveRecorder(synth, waveFile); + } catch (FileNotFoundException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + LOGGER.debug("Writing to WAV file " + waveFile.getAbsolutePath()); + } + + @Override + public void start() { + // Start synthesizer using default stereo output at 44100 Hz. + synth.start(); + lineOut.start(); + + reference.output.connect(0, recorder.getInput(), 0); + filterMoog.output.connect(0, recorder.getInput(), 1); + recorder.start(); + } + + @Override + public void stop() { + if (recorder != null) { + recorder.stop(); + try { + recorder.close(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + lineOut.stop(); + synth.stop(); + } + + public void test() { + init(); + start(); + try { + calibrate(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + stop(); + } + + private void calibrate() throws InterruptedException { + synth.sleepFor(0.2); + double freq = SWEEP_FREQUENCY ? MIN_FREQUENCY : DEFAULT_FREQUENCY; + double q = SWEEP_Q ? MIN_Q : DEFAULT_Q; + double stepQ = (MAX_Q - MIN_Q) / (NUM_STEPS - 1); + double scaleFrequency = Math.pow((MAX_FREQUENCY / MIN_FREQUENCY), (1.0 / (NUM_STEPS - 1))); + System.out.printf("freq, q, measured\n"); + for (int i = 0; i < NUM_STEPS; i++) { + double refAmp = reference.amplitude.get(); + reference.amplitude.set(0.0); + synth.sleepFor(0.1); + reference.amplitude.set(refAmp); + + System.out.printf("%8.2f, %6.3f, \n", freq, q); + filterMoog.frequency.set(freq); + reference.frequency.set(freq); + filterMoog.Q.set(q); + + synth.sleepFor(2.0); + + if (SWEEP_FREQUENCY) { + freq *= scaleFrequency; + } + if (SWEEP_Q) { + q += stepQ; + } + } + } + + public static void main(String[] args) { + new RecordMoogFilter().test(); + } + +} diff --git a/src/test/java/com/jsyn/unitgen/TestConnections.java b/src/test/java/com/jsyn/unitgen/TestConnections.java new file mode 100644 index 0000000..9aee32f --- /dev/null +++ b/src/test/java/com/jsyn/unitgen/TestConnections.java @@ -0,0 +1,111 @@ +/* + * 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.unitgen; + +import com.jsyn.JSyn; +import com.jsyn.Synthesizer; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class TestConnections { + private Synthesizer synth; + + private Add add1; + private Add add2; + private Add add3; + + @BeforeEach + private void beforeEach() { + synth = JSyn.createSynthesizer(); + + synth.add(add1 = new Add()); + synth.add(add2 = new Add()); + synth.add(add3 = new Add()); + + add1.start(); + add2.start(); + add3.start(); + + add1.inputA.set(0.1); + add1.inputB.set(0.2); + + add2.inputA.set(0.4); + add2.inputB.set(0.8); + + add3.inputA.set(1.6); + add3.inputB.set(3.2); + } + + @Test + public void testSet() throws InterruptedException { + synth.sleepFor(0.01); + assertEquals(0.3, add1.output.getValue(), 0.0001, "set inputs of adder"); + } + + @Test + public void testConnect() throws InterruptedException { + synth.sleepFor(0.01); + assertEquals(0.3, add1.output.getValue(), 0.0001, "set inputs of adder"); + assertEquals(1.2, add2.output.getValue(), 0.0001, "set inputs of adder"); + + // Test different ways of connecting. + add1.output.connect(add2.inputB); + checkConnection(); + + add1.output.connect(0, add2.inputB, 0); + checkConnection(); + + add1.output.connect(add2.inputB.getConnectablePart(0)); + checkConnection(); + + add1.output.getConnectablePart(0).connect(add2.inputB); + checkConnection(); + + add1.output.getConnectablePart(0).connect(add2.inputB.getConnectablePart(0)); + checkConnection(); + + add2.inputB.connect(add1.output); + checkConnection(); + + add2.inputB.connect(0, add1.output, 0); + checkConnection(); + + add2.inputB.connect(add1.output.getConnectablePart(0)); + checkConnection(); + + add2.inputB.getConnectablePart(0).connect(add1.output); + checkConnection(); + + add2.inputB.getConnectablePart(0).connect(add1.output.getConnectablePart(0)); + checkConnection(); + } + + private void checkConnection() throws InterruptedException { + synth.sleepFor(0.01); + assertEquals(0.3, add1.output.getValue(), 0.0001, "connection should not change output"); + assertEquals(0.7, add2.output.getValue(), 0.0001, "replace set value with output"); + + // Revert to set value after disconnection. + add1.output.disconnectAll(); + synth.sleepFor(0.01); + assertEquals(0.3, add1.output.getValue(), 0.0001, "still the same"); + assertEquals(1.2, add2.output.getValue(), 0.0001, "should revert to original set() value"); + } + +} diff --git a/src/test/java/com/jsyn/unitgen/TestDelay.java b/src/test/java/com/jsyn/unitgen/TestDelay.java new file mode 100644 index 0000000..7e1a0b1 --- /dev/null +++ b/src/test/java/com/jsyn/unitgen/TestDelay.java @@ -0,0 +1,81 @@ +/* + * 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.unitgen; + +import com.jsyn.util.AudioStreamReader; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class TestDelay extends NonRealTimeTestCase { + + @Test + public void testFloor() { + double x = -7.3; + int n = (int) Math.floor(x); + assertEquals(-8, n, "int"); + } + + public void checkInterpolatingDelay(int maxFrames, double delayFrames) + throws InterruptedException { + synthesisEngine.start(); + + System.out.printf("test delayFrames = %7.5f\n", delayFrames); + InterpolatingDelay delay = new InterpolatingDelay(); + synthesisEngine.add(delay); + delay.allocate(maxFrames); + delay.delay.set(delayFrames / 44100.0); + SawtoothOscillator osc = new SawtoothOscillator(); + synthesisEngine.add(osc); + osc.frequency.set(synthesisEngine.getFrameRate() / 4.0); + osc.amplitude.set(1.0); + osc.output.connect(delay.input); + + int samplesPerFrame = 1; + AudioStreamReader reader = new AudioStreamReader(synthesisEngine, samplesPerFrame); + delay.output.connect(reader.getInput()); + + delay.start(); + for (int i = 0; i < (3 * maxFrames); i++) { + if (reader.available() == 0) { + synthesisEngine.sleepFor(0.01); + } + double actual = reader.read(); + double expected = 1 + i - delayFrames; + if (expected < 0.0) { + expected = 0.0; + } + // System.out.printf( "[%d] expected = %7.3f, delayed = %7.3f\n", i, expected, actual ); + // assertEquals(expected, actual, 0.00001, "delayed output"); + } + } + + @Test + public void testSmall() throws InterruptedException { + checkInterpolatingDelay(40, 7.0); + } + + @Test + public void testEven() throws InterruptedException { + checkInterpolatingDelay(44100, 13671.0); + } + + @Test + public void testInterpolatingDelay() throws InterruptedException { + checkInterpolatingDelay(44100, 13671.4); + } +} diff --git a/src/test/java/com/jsyn/unitgen/TestEnable.java b/src/test/java/com/jsyn/unitgen/TestEnable.java new file mode 100644 index 0000000..a244c61 --- /dev/null +++ b/src/test/java/com/jsyn/unitgen/TestEnable.java @@ -0,0 +1,81 @@ +/* + * 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.unitgen; + +import com.jsyn.engine.SynthesisEngine; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; + +public class TestEnable { + private SynthesisEngine synthesisEngine; + + @BeforeEach + protected void beforeEach() { + synthesisEngine = new SynthesisEngine(); + synthesisEngine.setRealTime(false); + } + + @AfterEach + protected void afterEach() { + synthesisEngine.stop(); + } + + @Test + public void testEnablingGate() throws InterruptedException { + LinearRamp ramp = new LinearRamp(); + synthesisEngine.add(ramp); + EnablingGate enabler = new EnablingGate(); + synthesisEngine.add(enabler); + Add adder = new Add(); + synthesisEngine.add(adder); + + ramp.output.connect(enabler.input); + enabler.output.connect(adder.inputA); + + // set up so ramp should equal time + ramp.current.set(0.0); + ramp.input.set(1.0); + ramp.time.set(1.0); + enabler.gate.set(1.0); + + synthesisEngine.start(); + double startTime = synthesisEngine.getCurrentTime(); + // pull from final adder + adder.start(); + synthesisEngine.sleepUntil(startTime + 0.1); + double tolerance = 0.002; + assertEquals(0.1, ramp.output.getValue(), tolerance, "ramp going up"); + assertEquals(0.1, enabler.output.getValue(), tolerance, "enabler going up"); + assertEquals(0.1, adder.output.getValue(), tolerance, "adder going up"); + synthesisEngine.sleepUntil(startTime + 0.2); + assertEquals(0.2, adder.output.getValue(), tolerance, "start enabled"); + + // disable everything upstream + enabler.gate.set(0.0); + + synthesisEngine.sleepUntil(startTime + 0.3); + assertEquals(0.2, ramp.output.getValue(), tolerance, "should not be pulled"); + assertFalse(enabler.isEnabled(), "should be disabled"); + assertEquals(0.0, enabler.output.getValue(), tolerance, "should be zero"); + assertEquals(0.0, adder.output.getValue(), tolerance, "zero"); + + } +} diff --git a/src/test/java/com/jsyn/unitgen/TestEnvelopeAttackDecay.java b/src/test/java/com/jsyn/unitgen/TestEnvelopeAttackDecay.java new file mode 100644 index 0000000..8328bb7 --- /dev/null +++ b/src/test/java/com/jsyn/unitgen/TestEnvelopeAttackDecay.java @@ -0,0 +1,130 @@ +/* + * 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.unitgen; + +import com.jsyn.engine.SynthesisEngine; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class TestEnvelopeAttackDecay extends TestUnitGate { + double attackTime; + double decayTime; + + @BeforeEach + protected void beforeEach() { + synthesisEngine = new SynthesisEngine(); + synthesisEngine.setRealTime(false); + attackTime = 0.2; + decayTime = 0.4; + } + + @AfterEach + protected void afterEach() { + synthesisEngine.stop(); + } + + @Test + public void testOnOff() throws InterruptedException { + var envelope = new EnvelopeAttackDecay(); + synthesisEngine.add(envelope); + + envelope.attack.set(0.1); + envelope.decay.set(0.2); + + synthesisEngine.start(); + envelope.start(); + time = synthesisEngine.getCurrentTime(); + synthesisEngine.sleepUntil(time + 0.1); + assertEquals(0.0, envelope.output.getValue(), "still idling"); + + // Trigger the envelope using on/off + envelope.input.on(); + time = synthesisEngine.getCurrentTime(); + // Check end of attack cycle. + synthesisEngine.sleepUntil(time + 0.1); + assertTrue(envelope.output.getValue() > 0.8, "at peak"); + envelope.input.off(); + // Check end of decay cycle. + synthesisEngine.sleepUntil(time + 0.3); + assertTrue(envelope.output.getValue() < 0.1, "at peak"); + + synthesisEngine.sleepFor(0.1); + + // Trigger the envelope using trigger() + envelope.input.trigger(); + time = synthesisEngine.getCurrentTime(); + // Check end of attack cycle. + synthesisEngine.sleepUntil(time + 0.1); + assertTrue(envelope.output.getValue() > 0.8, "at peak"); + // Check end of decay cycle. + synthesisEngine.sleepUntil(time + 0.3); + assertTrue(envelope.output.getValue() < 0.1, "at peak"); + } + + @Test + public void testRetrigger() throws InterruptedException { + var envelope = new EnvelopeAttackDecay(); + synthesisEngine.add(envelope); + + envelope.attack.set(0.1); + envelope.decay.set(0.2); + + synthesisEngine.start(); + envelope.start(); + time = synthesisEngine.getCurrentTime(); + synthesisEngine.sleepUntil(time + 0.1); + assertEquals(0.0, envelope.output.getValue(), "still idling"); + + // Trigger the envelope using trigger() + envelope.input.trigger(); + // Check end of attack cycle. + synthesisEngine.sleepFor(0.1); + assertEquals(1.0, envelope.output.getValue(), 0.1, "at peak"); + + // Decay half way. + synthesisEngine.sleepFor(0.1); + assertTrue(envelope.output.getValue() < 0.7, "at peak"); + + // Retrigger while decaying + envelope.input.trigger(); + // Will get to top faster. + synthesisEngine.sleepFor(0.1); + assertEquals(1.0, envelope.output.getValue(), 0.1, "at peak"); + + // Check end of decay cycle. + synthesisEngine.sleepFor(0.2); + assertTrue(envelope.output.getValue() < 0.1, "at peak"); + + } + + @Test + public void testAutoDisable() throws InterruptedException { + var ramp = new LinearRamp(); + synthesisEngine.add(ramp); + var envelope = new EnvelopeAttackDecay(); + envelope.attack.set(0.1); + envelope.decay.set(0.1); + synthesisEngine.add(envelope); + ramp.output.connect(envelope.amplitude); + + checkAutoDisable(ramp, envelope); + } +} diff --git a/src/test/java/com/jsyn/unitgen/TestEnvelopeDAHDSR.java b/src/test/java/com/jsyn/unitgen/TestEnvelopeDAHDSR.java new file mode 100644 index 0000000..618e823 --- /dev/null +++ b/src/test/java/com/jsyn/unitgen/TestEnvelopeDAHDSR.java @@ -0,0 +1,355 @@ +/* + * 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.unitgen; + +import com.jsyn.engine.SynthesisEngine; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class TestEnvelopeDAHDSR extends TestUnitGate { + + private static final Logger LOGGER = LoggerFactory.getLogger(TestEnvelopeDAHDSR.class); + + double delayTime; + double attackTime; + double holdTime; + double decayTime; + double sustainLevel; + double releaseTime; + + @BeforeEach + protected void beforeEach() { + synthesisEngine = new SynthesisEngine(); + synthesisEngine.setRealTime(false); + delayTime = 0.1; + attackTime = 0.2; + holdTime = 0.3; + decayTime = 0.4; + sustainLevel = 0.5; + releaseTime = 0.6; + } + + @AfterEach + protected void afterEach() { + synthesisEngine.stop(); + } + + @Test + public void testStages() throws InterruptedException { + EnvelopeDAHDSR ramp = checkToSustain(); + + // Change sustain level to simulate tremolo sustain. + sustainLevel = 0.7; + ramp.sustain.set(sustainLevel); + time += 0.01; + synthesisEngine.sleepUntil(time); + assertEquals(sustainLevel, ramp.output.getValue(), 0.01, "sustain moving delaying"); + + // Gate off to let envelope release. + ramp.input.set(0.0); + synthesisEngine.sleepUntil(time + (releaseTime * 0.1)); + double releaseValue = ramp.output.getValue(); + assertEquals(sustainLevel * 0.36, releaseValue, 0.01, "partway down release"); + } + + private EnvelopeDAHDSR checkToSustain() throws InterruptedException { + EnvelopeDAHDSR ramp = new EnvelopeDAHDSR(); + synthesisEngine.add(ramp); + + ramp.delay.set(delayTime); + ramp.attack.set(attackTime); + ramp.hold.set(holdTime); + ramp.decay.set(decayTime); + ramp.sustain.set(sustainLevel); + ramp.release.set(releaseTime); + + synthesisEngine.start(); + ramp.start(); + time = synthesisEngine.getCurrentTime(); + synthesisEngine.sleepUntil(time + (2.0 * delayTime)); + assertEquals(0.0, ramp.output.getValue(), "still idling"); + + // Trigger the envelope. + ramp.input.set(1.0); + time = synthesisEngine.getCurrentTime(); + // Check end of delay cycle. + synthesisEngine.sleepUntil(time + (delayTime * 0.9)); + assertEquals(0.0, ramp.output.getValue(), 0.01, "still delaying"); + // Half way up attack ramp. + synthesisEngine.sleepUntil(time + delayTime + (attackTime * 0.5)); + assertEquals(0.5, ramp.output.getValue(), 0.01, "half attack"); + // Holding after attack. + synthesisEngine.sleepUntil(time + delayTime + attackTime + (holdTime * 0.1)); + assertEquals(1.0, ramp.output.getValue(), 0.01, "holding"); + synthesisEngine.sleepUntil(time + delayTime + attackTime + (holdTime * 0.9)); + assertEquals(1.0, ramp.output.getValue(), 0.01, "still holding"); + synthesisEngine.sleepUntil(time + delayTime + attackTime + holdTime + decayTime); + time = synthesisEngine.getCurrentTime(); + assertEquals(sustainLevel, ramp.output.getValue(), 0.01, "at sustain"); + return ramp; + } + + @Test + public void testRetrigger() throws InterruptedException { + EnvelopeDAHDSR ramp = checkToSustain(); + + // Gate off to let envelope release. + ramp.input.set(0.0); + synthesisEngine.sleepUntil(time + (releaseTime * 0.1)); + double releaseValue = ramp.output.getValue(); + assertEquals(sustainLevel * 0.36, releaseValue, 0.01, "partway down release"); + + // Retrigger during release phase. + time = synthesisEngine.getCurrentTime(); + ramp.input.set(1.0); + // Check end of delay cycle. + synthesisEngine.sleepUntil(time + (delayTime * 0.9)); + assertEquals(releaseValue, ramp.output.getValue(), 0.01, "still delaying"); + // Half way up attack ramp from where it started. + synthesisEngine.sleepUntil(time + delayTime + (attackTime * 0.5)); + assertEquals(releaseValue + 0.5, ramp.output.getValue(), 0.01, "half attack"); + + } + + // I noticed a hang while playing with knobs. + @Test + public void testHang() throws InterruptedException { + + delayTime = 0.0; + attackTime = 0.0; + holdTime = 0.0; + decayTime = 0.0; + sustainLevel = 0.3; + releaseTime = 3.0; + + EnvelopeDAHDSR ramp = new EnvelopeDAHDSR(); + synthesisEngine.add(ramp); + + ramp.delay.set(delayTime); + ramp.attack.set(attackTime); + ramp.hold.set(holdTime); + ramp.decay.set(decayTime); + ramp.sustain.set(sustainLevel); + ramp.release.set(releaseTime); + + synthesisEngine.start(); + ramp.start(); + // Trigger the envelope. + ramp.input.set(1.0); + time = synthesisEngine.getCurrentTime(); + synthesisEngine.sleepUntil(time + 0.01); + assertEquals(sustainLevel, ramp.output.getValue(), "should jump to sustain level"); + + // Gate off to let envelope release. + ramp.input.set(0.0); + synthesisEngine.sleepUntil(time + 1.0); + double releaseValue = ramp.output.getValue(); + assertTrue(sustainLevel > releaseValue, "partway down release"); + + holdTime = 0.5; + ramp.hold.set(holdTime); + decayTime = 0.5; + ramp.decay.set(decayTime); + + // Retrigger during release phase and try to catch it at top of hold + time = synthesisEngine.getCurrentTime(); + ramp.input.set(1.0); + // Check end of delay cycle. + synthesisEngine.sleepUntil(time + (holdTime * 0.1)); + assertEquals(1.0, ramp.output.getValue(), 0.01, "should jump to hold"); + } + + @Test + public void testNegative() throws InterruptedException { + delayTime = -0.1; + attackTime = -0.2; + holdTime = -0.3; + decayTime = -0.4; + sustainLevel = 0.3; + releaseTime = -0.5; + + EnvelopeDAHDSR ramp = new EnvelopeDAHDSR(); + synthesisEngine.add(ramp); + + ramp.delay.set(delayTime); + ramp.attack.set(attackTime); + ramp.hold.set(holdTime); + ramp.decay.set(decayTime); + ramp.sustain.set(sustainLevel); + ramp.release.set(releaseTime); + + synthesisEngine.start(); + ramp.start(); + // Trigger the envelope. + ramp.input.set(1.0); + time = synthesisEngine.getCurrentTime(); + time += 0.1; + synthesisEngine.sleepUntil(time + 0.01); + assertEquals(sustainLevel, ramp.output.getValue(), "should jump to sustain level"); + + ramp.sustain.set(sustainLevel = -0.4); + time += 0.1; + synthesisEngine.sleepUntil(time); + assertEquals(sustainLevel, ramp.output.getValue(), "sustain should clip at zero"); + + ramp.sustain.set(sustainLevel = 0.4); + time += 0.1; + synthesisEngine.sleepUntil(time); + assertEquals(sustainLevel, ramp.output.getValue(), "sustain should come back"); + + // Gate off to let envelope release. + ramp.input.set(0.0); + time += 0.1; + synthesisEngine.sleepUntil(time); + double releaseValue = ramp.output.getValue(); + assertEquals(0.0, releaseValue, "release quickly"); + } + + @Test + public void testOnOff() throws InterruptedException { + EnvelopeDAHDSR ramp = new EnvelopeDAHDSR(); + synthesisEngine.add(ramp); + + ramp.delay.set(0.0); + ramp.attack.set(0.1); + ramp.hold.set(0.0); + ramp.decay.set(0.0); + ramp.sustain.set(0.9); + ramp.release.set(0.1); + + synthesisEngine.start(); + ramp.start(); + time = synthesisEngine.getCurrentTime(); + synthesisEngine.sleepUntil(time + 0.2); + assertEquals(0.0, ramp.output.getValue(), "still idling"); + + // Trigger the envelope. + ramp.input.on(); + time = synthesisEngine.getCurrentTime(); + // Check end of delay cycle. + synthesisEngine.sleepUntil(time + 0.2); + assertEquals(0.9, ramp.output.getValue(), 0.01, "at sustain"); + + // Release the envelope. + ramp.input.off(); + time = synthesisEngine.getCurrentTime(); + // Check end of delay cycle. + synthesisEngine.sleepUntil(time + 0.2); + assertEquals(0.0, ramp.output.getValue(), 0.01, "after release"); + } + + @Test + public void testAutoDisable() throws InterruptedException { + + LinearRamp ramp = new LinearRamp(); + synthesisEngine.add(ramp); + EnvelopeDAHDSR envelope = new EnvelopeDAHDSR(); + synthesisEngine.add(envelope); + envelope.attack.set(0.1); + envelope.decay.set(0.1); + envelope.release.set(0.1); + envelope.sustain.set(0.1); + ramp.output.connect(envelope.amplitude); + + checkAutoDisable(ramp, envelope); + } + + static class GatedRampCircuit extends Circuit { + LinearRamp ramp; + EnvelopeDAHDSR envelope; + + GatedRampCircuit() { + add(ramp = new LinearRamp()); + add(envelope = new EnvelopeDAHDSR()); + envelope.attack.set(0.1); + envelope.decay.set(0.1); + envelope.release.set(0.1); + envelope.sustain.set(0.1); + + envelope.setupAutoDisable(this); + ramp.output.connect(envelope.amplitude); + } + } + + @Test + public void testAutoDisableCircuit() throws InterruptedException { + GatedRampCircuit circuit = new GatedRampCircuit(); + synthesisEngine.add(circuit); + checkAutoDisable(circuit.ramp, circuit.envelope); + } + + public void checkReleaseTiming(double releaseTime, double tolerance) + throws InterruptedException { + delayTime = 0.0; + attackTime = 0.2; + holdTime = 0.0; + decayTime = 10.0; + sustainLevel = 1.0; + + EnvelopeDAHDSR ramp = new EnvelopeDAHDSR(); + synthesisEngine.add(ramp); + + ramp.delay.set(delayTime); + ramp.attack.set(attackTime); + ramp.hold.set(holdTime); + ramp.decay.set(decayTime); + ramp.sustain.set(sustainLevel); + ramp.release.set(releaseTime); + + synthesisEngine.start(); + ramp.start(); + // Trigger the envelope. + ramp.input.set(1.0); + time = synthesisEngine.getCurrentTime(); + time += attackTime * 2; + synthesisEngine.sleepUntil(time); + assertEquals(sustainLevel, ramp.output.getValue(), "should be at to sustain level"); + + // Start envelope release. + ramp.input.set(0.0); + final double db90 = 20.0 * Math.log(1.0 / 32768.0) / Math.log(10.0); + LOGGER.debug("JSyns DB90 is actually " + db90); + int numSteps = 10; + for (int i = 0; i < 10; i++) { + time += releaseTime / numSteps; + synthesisEngine.sleepUntil(time); + double expectedDB = db90 * (i + 1) / numSteps; + double expectedAmplitude = sustainLevel * Math.pow(10.0, expectedDB / 20.0); + double releaseValue = ramp.output.getValue(); + assertEquals(expectedAmplitude, releaseValue, tolerance, "release " + i + " at"); + } + time += releaseTime / numSteps; + synthesisEngine.sleepUntil(time); + double releaseValue = ramp.output.getValue(); + assertEquals(0.0, releaseValue, 0.0001, "env after release time should go to zero"); + } + + @Test + public void testReleaseTiming() throws InterruptedException { + checkReleaseTiming(0.1, 0.004); + checkReleaseTiming(1.0, 0.002); + checkReleaseTiming(2.5, 0.001); + checkReleaseTiming(10.0, 0.001); + } + +} diff --git a/src/test/java/com/jsyn/unitgen/TestFunction.java b/src/test/java/com/jsyn/unitgen/TestFunction.java new file mode 100644 index 0000000..65953cd --- /dev/null +++ b/src/test/java/com/jsyn/unitgen/TestFunction.java @@ -0,0 +1,77 @@ +/* + * 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.unitgen; + +import com.jsyn.JSyn; +import com.jsyn.Synthesizer; +import com.jsyn.data.DoubleTable; +import com.jsyn.data.Function; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * @author Phil Burk, (C) 2009 Mobileer Inc + */ +public class TestFunction { + Synthesizer synth; + + @BeforeEach + protected void beforeEach() { + synth = JSyn.createSynthesizer(); + synth.setRealTime(false); + synth.start(); + } + + @AfterEach + protected void afterEach() { + synth.stop(); + } + + @Test + public void testDoubleTable() { + double[] data = { + 2.0, 0.0, 3.0 + }; + DoubleTable table = new DoubleTable(data); + assertEquals(2.0, table.evaluate(-1.4), "DoubleTable below"); + assertEquals(2.0, table.evaluate(-1.0), "DoubleTable edge"); + assertEquals(1.0, table.evaluate(-0.5), "DoubleTable mid"); + assertEquals(0.0, table.evaluate(0.0), "DoubleTable zero"); + assertEquals(0.75, table.evaluate(0.25), "DoubleTable mid"); + assertEquals(3.0, table.evaluate(1.3), "DoubleTable above"); + + } + + @Test + public void testFunctionEvaluator() throws InterruptedException { + FunctionEvaluator shaper = new FunctionEvaluator(); + synth.add(shaper); + shaper.start(); + + Function cuber = x -> x * x * x; + shaper.function.set(cuber); + + shaper.input.set(0.5); + synth.sleepFor(0.001); + + assertEquals((0.5 * 0.5 * 0.5), shaper.output.getValue(), "Cuber"); + } + +} diff --git a/src/test/java/com/jsyn/unitgen/TestMath.java b/src/test/java/com/jsyn/unitgen/TestMath.java new file mode 100644 index 0000000..7c33223 --- /dev/null +++ b/src/test/java/com/jsyn/unitgen/TestMath.java @@ -0,0 +1,420 @@ +/* + * 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.unitgen; + +import com.jsyn.engine.SynthesisEngine; +import com.softsynth.math.AudioMath; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * @author Phil Burk, (C) 2009 Mobileer Inc + */ +public class TestMath { + SynthesisEngine synthesisEngine; + + @BeforeEach + protected void beforeEach() { + synthesisEngine = new SynthesisEngine(); + } + + @Test + public void testAdd() { + Add add = new Add(); + add.setSynthesisEngine(synthesisEngine); + + double x = 33.99; + double y = 8.31; + add.inputA.setValueInternal(x); + add.inputB.setValueInternal(y); + + add.generate(); + + assertEquals(x + y, add.output.getValue(), 0.001, "Add"); + } + + @Test + public void testPartialAdd() { + Add add = new Add(); + add.setSynthesisEngine(synthesisEngine); + + double x = 2.5; + double y = 9.7; + add.inputA.setValueInternal(x); + add.inputB.setValueInternal(y); + + // Only generate a few values in the middle. + // This is to test low latency feedback loops. + // Only generate values for 2,3,4 + add.generate(2, 5); + + assertEquals(0.0, add.output.getValues()[0], 0.001, "Add partial"); + assertEquals(0.0, add.output.getValues()[1], 0.001, "Add partial"); + assertEquals(x + y, add.output.getValues()[2], 0.001, "Add partial"); + assertEquals(x + y, add.output.getValues()[3], 0.001, "Add partial"); + assertEquals(x + y, add.output.getValues()[4], 0.001, "Add partial"); + assertEquals(0.0, add.output.getValues()[5], 0.001, "Add partial"); + assertEquals(0.0, add.output.getValues()[6], 0.001, "Add partial"); + assertEquals(0.0, add.output.getValues()[7], 0.001, "Add partial"); + + } + + /** + * Unit test for Subtract.java - added by Lisa Tolentino 06/17/2009 + */ + @Test + public void testSubtract() { + Subtract sub = new Subtract(); + sub.setSynthesisEngine(synthesisEngine); + + double x = 33.99; + double y = 8.31; + sub.inputA.setValueInternal(x); + sub.inputB.setValueInternal(y); + + sub.generate(); + + assertEquals(x - y, sub.output.getValue(), 0.001, "Subtract"); + } + + @Test + public void testPartialSubtract() { + Subtract sub = new Subtract(); + sub.setSynthesisEngine(synthesisEngine); + + double x = 2.5; + double y = 9.7; + sub.inputA.setValueInternal(x); + sub.inputB.setValueInternal(y); + + // Only generate a few values in the middle. + // This is to test low latency feedback loops. + // Only generate values for 2,3,4 + sub.generate(2, 5); + + assertEquals(0.0, sub.output.getValues()[0], 0.001, "Subtract partial"); + assertEquals(0.0, sub.output.getValues()[1], 0.001, "Subtract partial"); + assertEquals(x - y, sub.output.getValues()[2], 0.001, "Subtract partial"); + assertEquals(x - y, sub.output.getValues()[3], 0.001, "Subtract partial"); + assertEquals(x - y, sub.output.getValues()[4], 0.001, "Subtract partial"); + assertEquals(0.0, sub.output.getValues()[5], 0.001, "Subtract partial"); + assertEquals(0.0, sub.output.getValues()[6], 0.001, "Subtract partial"); + assertEquals(0.0, sub.output.getValues()[7], 0.001, "Subtract partial"); + } + + /** + * Unit test for Multiply.java - added by Lisa Tolentino 06/19/2009 + */ + @Test + public void testMultiply() { + Multiply mult = new Multiply(); + mult.setSynthesisEngine(synthesisEngine); + + double x = 33.99; + double y = 8.31; + mult.inputA.setValueInternal(x); + mult.inputB.setValueInternal(y); + + mult.generate(); + + assertEquals(x * y, mult.output.getValue(), 0.001, "Multiply"); + } + + @Test + public void testPartialMultiply() { + Multiply mult = new Multiply(); + mult.setSynthesisEngine(synthesisEngine); + + double x = 2.5; + double y = 9.7; + mult.inputA.setValueInternal(x); + mult.inputB.setValueInternal(y); + + // Only generate a few values in the middle. + // This is to test low latency feedback loops. + // Only generate values for 2,3,4 + mult.generate(2, 5); + + assertEquals(0.0, mult.output.getValues()[0], 0.001, "Multiply partial"); + assertEquals(0.0, mult.output.getValues()[1], 0.001, "Multiply partial"); + assertEquals(x * y, mult.output.getValues()[2], 0.001, "Multiply partial"); + assertEquals(x * y, mult.output.getValues()[3], 0.001, "Multiply partial"); + assertEquals(x * y, mult.output.getValues()[4], 0.001, "Multiply partial"); + assertEquals(0.0, mult.output.getValues()[5], 0.001, "Multiply partial"); + assertEquals(0.0, mult.output.getValues()[6], 0.001, "Multiply partial"); + assertEquals(0.0, mult.output.getValues()[7], 0.001, "Multiply partial"); + } + + /** + * Unit test for Divide.java - added by Lisa Tolentino 06/19/2009 + */ + @Test + public void testDivide() { + Divide divide = new Divide(); + divide.setSynthesisEngine(synthesisEngine); + + double x = 33.99; + double y = 8.31; + divide.inputA.setValueInternal(x); + divide.inputB.setValueInternal(y); + + divide.generate(); + + assertEquals(x / y, divide.output.getValue(), 0.001, "Divide"); + } + + @Test + public void testPartialDivide() { + Divide divide = new Divide(); + divide.setSynthesisEngine(synthesisEngine); + + double x = 2.5; + double y = 9.7; + divide.inputA.setValueInternal(x); + divide.inputB.setValueInternal(y); + + // Only generate a few values in the middle. + // This is to test low latency feedback loops. + // Only generate values for 2,3,4 + divide.generate(2, 5); + + assertEquals(0.0, divide.output.getValues()[0], 0.001, "Divide partial"); + assertEquals(0.0, divide.output.getValues()[1], 0.001, "Divide partial"); + assertEquals(x / y, divide.output.getValues()[2], 0.001, "Divide partial"); + assertEquals(x / y, divide.output.getValues()[3], 0.001, "Divide partial"); + assertEquals(x / y, divide.output.getValues()[4], 0.001, "Divide partial"); + assertEquals(0.0, divide.output.getValues()[5], 0.001, "Divide partial"); + assertEquals(0.0, divide.output.getValues()[6], 0.001, "Divide partial"); + assertEquals(0.0, divide.output.getValues()[7], 0.001, "Divide partial"); + } + + /** + * Unit test for MultiplyAdd.java - added by Lisa Tolentino 06/19/2009 + */ + @Test + public void testMultiplyAdd() { + MultiplyAdd multAdd = new MultiplyAdd(); + multAdd.setSynthesisEngine(synthesisEngine); + + double x = 33.99; + double y = 8.31; + double z = 2.28; + multAdd.inputA.setValueInternal(x); + multAdd.inputB.setValueInternal(y); + multAdd.inputC.setValueInternal(z); + + multAdd.generate(); + + assertEquals((x * y) + z, multAdd.output.getValue(), 0.001, "MultiplyAdd"); + } + + @Test + public void testPartialMultiplyAdd() { + MultiplyAdd multAdd = new MultiplyAdd(); + multAdd.setSynthesisEngine(synthesisEngine); + + double x = 33.99; + double y = 8.31; + double z = 2.28; + multAdd.inputA.setValueInternal(x); + multAdd.inputB.setValueInternal(y); + multAdd.inputC.setValueInternal(z); + + // Only generate a few values in the middle. + // This is to test low latency feedback loops. + // Only generate values for 2,3,4 + multAdd.generate(2, 5); + + assertEquals(0.0, multAdd.output.getValues()[0], 0.001, "MultiplyAdd partial"); + assertEquals(0.0, multAdd.output.getValues()[1], 0.001, "MultiplyAdd partial"); + assertEquals((x * y) + z, multAdd.output.getValues()[2], 0.001, "MultiplyAdd partial"); + assertEquals((x * y) + z, multAdd.output.getValues()[3], 0.001, "MultiplyAdd partial"); + assertEquals((x * y) + z, multAdd.output.getValues()[4], 0.001, "MultiplyAdd partial"); + assertEquals(0.0, multAdd.output.getValues()[5], 0.001, "MultiplyAdd partial"); + assertEquals(0.0, multAdd.output.getValues()[6], 0.001, "MultiplyAdd partial"); + assertEquals(0.0, multAdd.output.getValues()[7], 0.001, "MultiplyAdd partial"); + } + + /** + * Unit test for Compare.java - added by Lisa Tolentino 06/19/2009 + */ + @Test + public void testCompare() { + UnitBinaryOperator compare = new Compare(); + compare.setSynthesisEngine(synthesisEngine); + + double x = 33.99; + double y = 8.31; + compare.inputA.setValueInternal(x); + compare.inputB.setValueInternal(y); + + compare.generate(); + + assertEquals((x > y ? 1 : 0), compare.output.getValue(), 0.001, "Compare"); + } + + @Test + public void testPartialCompare() { + UnitBinaryOperator compare = new Compare(); + compare.setSynthesisEngine(synthesisEngine); + + double x = 33.99; + double y = 8.31; + compare.inputA.setValueInternal(x); + compare.inputB.setValueInternal(y); + + // Only generate a few values in the middle. + // This is to test low latency feedback loops. + // Only generate values for 2,3,4 + compare.generate(2, 5); + + assertEquals(0.0, compare.output.getValues()[0], 0.001, "Compare partial"); + assertEquals(0.0, compare.output.getValues()[1], 0.001, "Compare partial"); + assertEquals((x > y ? 1 : 0), compare.output.getValues()[2], 0.001, "Compare partial"); + assertEquals((x > y ? 1 : 0), compare.output.getValues()[3], 0.001, "Compare partial"); + assertEquals((x > y ? 1 : 0), compare.output.getValues()[4], 0.001, "Compare partial"); + assertEquals(0.0, compare.output.getValues()[5], 0.001, "Compare partial"); + assertEquals(0.0, compare.output.getValues()[6], 0.001, "Compare partial"); + assertEquals(0.0, compare.output.getValues()[7], 0.001, "Compare partial"); + } + + /** + * Unit test for Maximum.java - added by Lisa Tolentino 06/20/2009 + */ + @Test + public void testMaximum() { + Maximum max = new Maximum(); + max.setSynthesisEngine(synthesisEngine); + + double x = 33.99; + double y = 8.31; + max.inputA.setValueInternal(x); + max.inputB.setValueInternal(y); + + max.generate(); + + assertEquals((x > y ? x : y), max.output.getValue(), 0.001, "Maximum"); + } + + @Test + public void testPartialMaximum() { + Maximum max = new Maximum(); + max.setSynthesisEngine(synthesisEngine); + + double x = 33.99; + double y = 8.31; + max.inputA.setValueInternal(x); + max.inputB.setValueInternal(y); + + // Only generate a few values in the middle. + // This is to test low latency feedback loops. + // Only generate values for 2,3,4 + max.generate(2, 5); + + assertEquals(0.0, max.output.getValues()[0], 0.001, "Maximum partial"); + assertEquals(0.0, max.output.getValues()[1], 0.001, "Maximum partial"); + assertEquals((x > y ? x : y), max.output.getValues()[2], 0.001, "Maximum partial"); + assertEquals((x > y ? x : y), max.output.getValues()[3], 0.001, "Maximum partial"); + assertEquals((x > y ? x : y), max.output.getValues()[4], 0.001, "Maximum partial"); + assertEquals(0.0, max.output.getValues()[5], 0.001, "Maximum partial"); + assertEquals(0.0, max.output.getValues()[6], 0.001, "Maximum partial"); + assertEquals(0.0, max.output.getValues()[7], 0.001, "Maximum partial"); + } + + /** + * Unit test for Minimum.java - added by Lisa Tolentino 06/20/2009 + */ + @Test + public void testMinimum() { + Minimum min = new Minimum(); + min.setSynthesisEngine(synthesisEngine); + + double x = 33.99; + double y = 8.31; + min.inputA.setValueInternal(x); + min.inputB.setValueInternal(y); + + min.generate(); + + assertEquals((x < y ? x : y), min.output.getValue(), 0.001, "Minimum"); + } + + @Test + public void testPartialMinimum() { + Minimum min = new Minimum(); + min.setSynthesisEngine(synthesisEngine); + + double x = 33.99; + double y = 8.31; + min.inputA.setValueInternal(x); + min.inputB.setValueInternal(y); + + // Only generate a few values in the middle. + // This is to test low latency feedback loops. + // Only generate values for 2,3,4 + min.generate(2, 5); + + assertEquals(0.0, min.output.getValues()[0], 0.001, "Maximum partial"); + assertEquals(0.0, min.output.getValues()[1], 0.001, "Maximum partial"); + assertEquals((x < y ? x : y), min.output.getValues()[2], 0.001, "Maximum partial"); + assertEquals((x < y ? x : y), min.output.getValues()[3], 0.001, "Maximum partial"); + assertEquals((x < y ? x : y), min.output.getValues()[4], 0.001, "Maximum partial"); + assertEquals(0.0, min.output.getValues()[5], 0.001, "Maximum partial"); + assertEquals(0.0, min.output.getValues()[6], 0.001, "Maximum partial"); + assertEquals(0.0, min.output.getValues()[7], 0.001, "Maximum partial"); + } + + @Test + public void testPowerOfTwo() { + PowerOfTwo powerOfTwo = new PowerOfTwo(); + powerOfTwo.setSynthesisEngine(synthesisEngine); + final double smallValue = -1.5308084989341915E-17; + double[] values = { + 0.0, 1.3, 4.5, -0.5, -1.0, -2.8, smallValue, -smallValue, 1.0 - smallValue, + 1.0 + smallValue + }; + for (double in : values) { + powerOfTwo.input.setValueInternal(in); + powerOfTwo.generate(); + assertEquals(Math.pow(2.0, in), powerOfTwo.output.getValue(), 0.001, "PowerOfTwo"); + } + } + + @Test + public void testPitchToFrequency() { + PitchToFrequency ugen = new PitchToFrequency(); + ugen.setSynthesisEngine(synthesisEngine); + final double smallValue = -1.5308084989341915E-17; + double[] values = { + 49.0, 49.5, 50.0 + smallValue, + 60.0 -smallValue, + 79.2, 12.9, 118.973 + }; + // Sanity check AudioMath + assertEquals(440.0, AudioMath.pitchToFrequency(69), 0.001, "PitchToFrequency"); + assertEquals(660.0, AudioMath.pitchToFrequency(69+7.02), 0.1, "PitchToFrequency"); + + for (double pitch : values) { + ugen.input.setValueInternal(pitch); + ugen.generate(); + assertEquals(ugen.output.getValue(), 0.001, "PitchToFrequency: " + AudioMath.pitchToFrequency(pitch)); + } + } + +} diff --git a/src/test/java/com/jsyn/unitgen/TestRamps.java b/src/test/java/com/jsyn/unitgen/TestRamps.java new file mode 100644 index 0000000..40d968c --- /dev/null +++ b/src/test/java/com/jsyn/unitgen/TestRamps.java @@ -0,0 +1,205 @@ +/* + * 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.unitgen; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class TestRamps extends NonRealTimeTestCase { + + public void viewContinuousRamp(double duration, double startValue, double targetValue) + throws InterruptedException { + ContinuousRamp ramp = new ContinuousRamp(); + synthesisEngine.add(ramp); + + ramp.current.set(startValue); + ramp.input.set(startValue); + ramp.time.set(duration); + + synthesisEngine.setRealTime(false); + synthesisEngine.start(); + ramp.start(); + synthesisEngine.sleepUntil(synthesisEngine.getCurrentTime() + 0.01); + ramp.input.set(targetValue); + + double time = synthesisEngine.getCurrentTime(); + int numLoops = 20; + double increment = duration / numLoops; + for (int i = 0; i < (numLoops + 1); i++) { + double value = ramp.output.getValue(); + System.out.printf("i = %d, t = %9.5f, value = %8.4f\n", i, time, value); + time += increment; + synthesisEngine.sleepUntil(time); + } + + synthesisEngine.stop(); + } + + public void checkContinuousRamp(double duration, double startValue, double targetValue) + throws InterruptedException { + ContinuousRamp ramp = new ContinuousRamp(); + synthesisEngine.add(ramp); + + ramp.current.set(startValue); + ramp.input.set(startValue); + ramp.time.set(duration); + + synthesisEngine.setRealTime(false); + synthesisEngine.start(); + ramp.start(); + synthesisEngine.sleepUntil(synthesisEngine.getCurrentTime() + 0.01); + assertEquals(ramp.input.getValue(), ramp.output.getValue(), "start flat"); + + ramp.input.set(targetValue); + double startTime = synthesisEngine.getCurrentTime(); + synthesisEngine.sleepUntil(startTime + (duration / 2)); + assertEquals((targetValue + startValue) / 2.0, ramp.output.getValue(), 0.01, "ramping up"); + synthesisEngine.sleepUntil(startTime + duration); + assertEquals(targetValue, ramp.output.getValue(), 0.01, "ramping up"); + synthesisEngine.sleepUntil(startTime + duration + 0.1); + assertEquals(targetValue, ramp.output.getValue(), "flat again"); + + synthesisEngine.stop(); + } + + @Test + public void testContinuousRamp() throws InterruptedException { + viewContinuousRamp(4.0, 0.0, 1.0); + } + + @Test + public void testExponentialRamp() throws InterruptedException { + ExponentialRamp ramp = new ExponentialRamp(); + synthesisEngine.add(ramp); + + double duration = 0.3; + ramp.current.set(1.0); + ramp.input.set(1.0); + ramp.time.set(duration); + + synthesisEngine.start(); + ramp.start(); + synthesisEngine.sleepUntil(synthesisEngine.getCurrentTime() + 0.01); + assertEquals(ramp.input.getValue(), ramp.output.getValue(), "start flat"); + + ramp.input.set(8.0); + double startTime = synthesisEngine.getCurrentTime(); + synthesisEngine.sleepUntil(startTime + 0.1); + assertEquals(2.0, ramp.output.getValue(), 0.01, "ramping up"); + synthesisEngine.sleepUntil(startTime + 0.2); + assertEquals(4.0, ramp.output.getValue(), 0.01, "ramping up"); + synthesisEngine.sleepUntil(startTime + 0.3); + assertEquals(8.0, ramp.output.getValue(), 0.01, "ramping up"); + synthesisEngine.sleepUntil(startTime + 0.4); + assertEquals(8.0, ramp.output.getValue(), "flat again"); + } + + @Test + public void testLinearRamp() throws InterruptedException { + LinearRamp ramp = new LinearRamp(); + synthesisEngine.add(ramp); + + double duration = 0.4; + ramp.current.set(0.0); + ramp.input.set(0.0); + ramp.time.set(duration); + + synthesisEngine.start(); + ramp.start(); + synthesisEngine.sleepUntil(synthesisEngine.getCurrentTime() + 0.01); + assertEquals(ramp.input.getValue(), ramp.output.getValue(), "start flat"); + + ramp.input.set(8.0); + double startTime = synthesisEngine.getCurrentTime(); + synthesisEngine.sleepUntil(startTime + 0.1); + assertEquals(2.0, ramp.output.getValue(), 0.01, "ramping up"); + synthesisEngine.sleepUntil(startTime + 0.2); + assertEquals(4.0, ramp.output.getValue(), 0.01, "ramping up"); + synthesisEngine.sleepUntil(startTime + 0.3); + assertEquals(6.0, ramp.output.getValue(), 0.01, "ramping up"); + synthesisEngine.sleepUntil(startTime + 0.4); + assertEquals(8.0, ramp.output.getValue(), "flat again"); + } + + @Test + public void testExponentialRampConnected() throws InterruptedException { + ExponentialRamp ramp = new ExponentialRamp(); + PassThrough pass = new PassThrough(); + synthesisEngine.add(ramp); + synthesisEngine.add(pass); + + double duration = 0.3; + ramp.current.set(1.0); + pass.input.set(1.0); + ramp.time.set(duration); + + // Send value through a connected unit. + pass.input.set(1.0); + pass.output.connect(ramp.input); + + synthesisEngine.start(); + ramp.start(); + synthesisEngine.sleepUntil(synthesisEngine.getCurrentTime() + 0.01); + assertEquals(ramp.input.getValue(), ramp.output.getValue(), "start flat"); + + pass.input.set(8.0); + double startTime = synthesisEngine.getCurrentTime(); + synthesisEngine.sleepUntil(startTime + 0.1); + assertEquals(2.0, ramp.output.getValue(), 0.01, "ramping up"); + synthesisEngine.sleepUntil(startTime + 0.2); + assertEquals(4.0, ramp.output.getValue(), 0.01, "ramping up"); + synthesisEngine.sleepUntil(startTime + 0.3); + assertEquals(8.0, ramp.output.getValue(), 0.01, "ramping up"); + synthesisEngine.sleepUntil(startTime + 0.4); + assertEquals(8.0, ramp.output.getValue(), "flat again"); + } + + @Test + public void testLinearRampConnected() throws InterruptedException { + LinearRamp ramp = new LinearRamp(); + PassThrough pass = new PassThrough(); + synthesisEngine.add(ramp); + synthesisEngine.add(pass); + + double duration = 0.4; + ramp.current.set(0.0); + pass.input.set(0.0); + ramp.time.set(duration); + + // Send value through a connected unit. + pass.input.set(0.0); + pass.output.connect(ramp.input); + + synthesisEngine.start(); + ramp.start(); + synthesisEngine.sleepUntil(synthesisEngine.getCurrentTime() + 0.01); + assertEquals(ramp.input.getValue(), ramp.output.getValue(), "start flat"); + + pass.input.set(8.0); + double startTime = synthesisEngine.getCurrentTime(); + synthesisEngine.sleepUntil(startTime + 0.1); + assertEquals(2.0, ramp.output.getValue(), 0.01, "ramping up"); + synthesisEngine.sleepUntil(startTime + 0.2); + assertEquals(4.0, ramp.output.getValue(), 0.01, "ramping up"); + synthesisEngine.sleepUntil(startTime + 0.3); + assertEquals(6.0, ramp.output.getValue(), 0.01, "ramping up"); + synthesisEngine.sleepUntil(startTime + 0.4); + assertEquals(8.0, ramp.output.getValue(), "flat again"); + } + +} diff --git a/src/test/java/com/jsyn/unitgen/TestUnitGate.java b/src/test/java/com/jsyn/unitgen/TestUnitGate.java new file mode 100644 index 0000000..cba03e7 --- /dev/null +++ b/src/test/java/com/jsyn/unitgen/TestUnitGate.java @@ -0,0 +1,81 @@ +/* + * 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.unitgen; + +import com.jsyn.engine.SynthesisEngine; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class TestUnitGate { + + protected SynthesisEngine synthesisEngine; + protected double time; + + public void checkAutoDisable(LinearRamp ramp, UnitGate envelope) throws InterruptedException { + double tolerance = 0.01; + Add adder = new Add(); + synthesisEngine.add(adder); + + envelope.output.connect(adder.inputA); + if (ramp.getCircuit() != null) { + ramp.output.connect(adder.inputB); + } + + envelope.input.setAutoDisableEnabled(true); + envelope.setEnabled(false); + + // set up so ramp value should equal time + ramp.current.set(0.0); + ramp.input.set(1.0); + ramp.time.set(1.0); + + synthesisEngine.start(); + // pull from final adder + adder.start(); + + time = synthesisEngine.getCurrentTime(); + time += 0.1; + synthesisEngine.sleepUntil(time); + assertEquals(0.0, envelope.output.getValue(), "still idling"); + assertEquals(0.0, ramp.output.getValue(), tolerance, "ramp frozen at beginning"); + + // run multiple times to make sure we can retrigger the envelope. + for (int i = 0; i < 3; i++) { + double level = ramp.output.getValue(); + // Trigger the envelope using trigger() + envelope.input.on(); + time += 0.1; + level += 0.1; + synthesisEngine.sleepUntil(time); + assertEquals(level, ramp.output.getValue(), tolerance, "ramp going up " + i); + assertTrue(envelope.isEnabled(), "enabled at peak"); + + envelope.input.off(); + time += 0.1; + level += 0.1; + synthesisEngine.sleepUntil(time); + assertEquals(level, ramp.output.getValue(), tolerance, "ramp going up more " + i); + assertEquals(0.0, envelope.output.getValue(), 0.1, "at bottom"); + + time += 0.2; + synthesisEngine.sleepUntil(time); + assertEquals(level, ramp.output.getValue(), tolerance, "ramp frozen " + i); + } + } + +} diff --git a/src/test/java/com/jsyn/util/DebugSampleLoader.java b/src/test/java/com/jsyn/util/DebugSampleLoader.java new file mode 100644 index 0000000..c0ddef5 --- /dev/null +++ b/src/test/java/com/jsyn/util/DebugSampleLoader.java @@ -0,0 +1,143 @@ +/* + * Copyright 2010 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.util; + +import java.io.File; +import java.io.IOException; + +import com.jsyn.JSyn; +import com.jsyn.Synthesizer; +import com.jsyn.data.FloatSample; +import com.jsyn.unitgen.LineOut; +import com.jsyn.unitgen.VariableRateDataReader; +import com.jsyn.unitgen.VariableRateMonoReader; +import com.jsyn.unitgen.VariableRateStereoReader; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Play a sample from a WAV file using JSyn. + * + * @author Phil Burk (C) 2010 Mobileer Inc + */ +public class DebugSampleLoader { + + private static final Logger LOGGER = LoggerFactory.getLogger(DebugSampleLoader.class); + + private Synthesizer synth; + private VariableRateDataReader samplePlayer; + private LineOut lineOut; + + private void test() throws IOException { + // File sampleFile = new File("samples/cello_markers.wav"); + // File sampleFile = new File("samples/Piano_A440_PT.aif"); + File sampleFile = new File("samples/sine_400_loop_i16.wav"); + // File sampleFile = new File("samples/TwoDiffPitchedSines_F32_PT.wav"); + // File sampleFile = new File("samples/sine_400_u8.aif"); + // File sampleFile = new File("samples/sine_400_s8.aif"); + // File sampleFile = new File("samples/sine_400_ulaw.aif"); + // File sampleFile = new File("samples/sine_400_ulaw.wav"); + + // File sampleFile = new File("samples/aaClarinet.wav"); + // File sampleFile = new File("samples/sine_400_mono.wav"); + // File sampleFile = new File("samples/sine_200_300_i16.wav"); + // File sampleFile = new File("samples/sine_200_300_i24.wav"); + // File sampleFile = new File("samples/M1F1-int16-AFsp.wav"); + // File sampleFile = new File("samples/M1F1-int24-AFsp.wav"); + // File sampleFile = new File("samples/M1F1-float32-AFsp.wav"); + // File sampleFile = new File("samples/M1F1-int16WE-AFsp.wav"); + // File sampleFile = new File("samples/M1F1-int24WE-AFsp.wav"); + // File sampleFile = new File("samples/M1F1-float32WE-AFsp.wav"); + // File sampleFile = new File("samples/sine_200_300_i16.aif"); + // File sampleFile = new File("samples/sine_200_300_f32.wavex"); + // File sampleFile = new File("samples/Sine32bit.aif"); + // File sampleFile = new File("samples/Sine32bit.wav"); + // File sampleFile = new File("samples/smartCue.wav"); + + // URL sampleFile = new URL("http://www.softsynth.com/samples/Clarinet.wav"); + + synth = JSyn.createSynthesizer(); + + FloatSample sample; + try { + // Add an output mixer. + synth.add(lineOut = new LineOut()); + + // Load the sample and display its properties. + SampleLoader.setJavaSoundPreferred(false); + sample = SampleLoader.loadFloatSample(sampleFile); + LOGGER.debug("Sample has: channels = " + sample.getChannelsPerFrame()); + LOGGER.debug(" frames = " + sample.getNumFrames()); + LOGGER.debug(" rate = " + sample.getFrameRate()); + LOGGER.debug(" loopStart = " + sample.getSustainBegin()); + LOGGER.debug(" loopEnd = " + sample.getSustainEnd()); + + if (sample.getChannelsPerFrame() == 1) { + synth.add(samplePlayer = new VariableRateMonoReader()); + samplePlayer.output.connect(0, lineOut.input, 0); + } else if (sample.getChannelsPerFrame() == 2) { + synth.add(samplePlayer = new VariableRateStereoReader()); + samplePlayer.output.connect(0, lineOut.input, 0); + samplePlayer.output.connect(1, lineOut.input, 1); + } else { + throw new RuntimeException("Can only play mono or stereo samples."); + } + + // Start synthesizer using default stereo output at 44100 Hz. + synth.start(); + + samplePlayer.rate.set(sample.getFrameRate()); + + // We only need to start the LineOut. It will pull data from the + // sample player. + lineOut.start(); + + // We can simply queue the entire file. + // Or if it has a loop we can play the loop for a while. + if (sample.getSustainBegin() < 0) { + LOGGER.debug("queue the sample"); + samplePlayer.dataQueue.queue(sample); + } else { + LOGGER.debug("queueOn the sample"); + samplePlayer.dataQueue.queueOn(sample); + synth.sleepFor(8.0); + LOGGER.debug("queueOff the sample"); + samplePlayer.dataQueue.queueOff(sample); + } + + // Wait until the sample has finished playing. + do { + synth.sleepFor(1.0); + } while (samplePlayer.dataQueue.hasMore()); + + } catch (IOException e1) { + e1.printStackTrace(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + // Stop everything. + synth.stop(); + } + + public static void main(String[] args) { + try { + new DebugSampleLoader().test(); + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/src/test/java/com/jsyn/util/TestFFT.java b/src/test/java/com/jsyn/util/TestFFT.java new file mode 100644 index 0000000..5d130c5 --- /dev/null +++ b/src/test/java/com/jsyn/util/TestFFT.java @@ -0,0 +1,201 @@ +/* + * 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.util; + +import com.softsynth.math.FourierMath; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class TestFFT { + + private static final Logger LOGGER = LoggerFactory.getLogger(TestFFT.class); + + public void checkSingleSineDouble(int size, int bin) { + double[] ar = new double[size]; + double[] ai = new double[size]; + double[] magnitudes = new double[size]; + + double amplitude = 1.0; + addSineWave(size, bin, ar, amplitude); + + FourierMath.transform(1, size, ar, ai); + FourierMath.calculateMagnitudes(ar, ai, magnitudes); + + assertEquals(0.0, magnitudes[bin-1], 0.000001, "magnitude"); + assertEquals(amplitude, magnitudes[bin], 0.000001, "magnitude"); + assertEquals(0.0, magnitudes[bin+1], 0.000001, "magnitude"); + /* + for (int i = 0; i < magnitudes.length; i++) { + System.out.printf("%d = %9.7f\n", i, magnitudes[i]); + } +*/ + + } + + public void checkSingleSineFloat(int size, int bin) { + float[] ar = new float[size]; + float[] ai = new float[size]; + float[] magnitudes = new float[size]; + + double amplitude = 1.0; + addSineWave(size, bin, ar, amplitude); + + FourierMath.transform(1, size, ar, ai); + FourierMath.calculateMagnitudes(ar, ai, magnitudes); + + assertEquals(0.0f, magnitudes[bin-1], 0.000001, "magnitude"); + assertEquals(amplitude, magnitudes[bin], 0.000001, "magnitude"); + assertEquals(0.0f, magnitudes[bin+1], 0.000001, "magnitude"); +/* + for (int i = 0; i < magnitudes.length; i++) { + System.out.printf("%d = %9.7f\n", i, magnitudes[i]); + } +*/ + } + + public void checkMultipleSine(int size, int[] bins, double[] amplitudes) { + double[] ar = new double[size]; + double[] ai = new double[size]; + double[] magnitudes = new double[size]; + + for(int i = 0; i= 0), "masked random positive, " + positiveInt); + double rand = positiveInt * (1.0 / (1L << 31)); + assertTrue((rand >= 0.0), "not too low, " + rand); + assertTrue((rand < 1.0), "not too high, " + rand); + } + + @Test + public void testIntegerDistribution() { + int scaler = 100; + for (int i = 0; i < (bins.length * scaler); i++) { + int rand = pseudoRandom.nextRandomInteger(); + int positiveInt = rand & 0x7FFFFFFF; + assertTrue((positiveInt >= 0), "masked random " + positiveInt); + int index = (rand >> (32 - BIN_SHIFTER)) & BIN_MASK; + bins[index] += 1; + } + checkDistribution(scaler); + } + + @Test + public void test01Distribution() { + int scaler = 100; + for (int i = 0; i < (bins.length * scaler); i++) { + double rand = pseudoRandom.random(); + assertTrue((rand >= 0.0), "not too low, #" + i + " = " + rand); + assertTrue((rand < 1.0), "not too high, #" + i + " = " + rand); + int index = (int) (rand * BIN_COUNT); + bins[index] += 1; + } + checkDistribution(scaler); + } + + private void checkDistribution(int scaler) { + // Generate running average that should stay near scaler + double average = scaler; + double coefficient = 0.9; + for (int i = 0; i < (bins.length); i++) { + average = (average * coefficient) + (bins[i] * (1.0 - coefficient)); + assertEquals(scaler, average, 0.2 * scaler, "average at " + i); + } + } +} diff --git a/src/test/java/com/jsyn/util/TestVoiceAllocator.java b/src/test/java/com/jsyn/util/TestVoiceAllocator.java new file mode 100644 index 0000000..061e2ae --- /dev/null +++ b/src/test/java/com/jsyn/util/TestVoiceAllocator.java @@ -0,0 +1,111 @@ +/* + * 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.util; + +import com.jsyn.instruments.SubtractiveSynthVoice; +import com.jsyn.unitgen.UnitVoice; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class TestVoiceAllocator { + VoiceAllocator allocator; + int max = 4; + UnitVoice[] voices; + + @BeforeEach + private void beforeEach() { + voices = new UnitVoice[max]; + for (int i = 0; i < max; i++) { + voices[i] = new SubtractiveSynthVoice(); + } + + allocator = new VoiceAllocator(voices); + } + + @Test + public void testAllocation() { + assertEquals(max, allocator.getVoiceCount(), "get max"); + + int tag1 = 61; + int tag2 = 62; + int tag3 = 63; + int tag4 = 64; + int tag5 = 65; + int tag6 = 66; + UnitVoice voice1 = allocator.allocate(tag1); + assertTrue((voice1 != null), "voice should be non-null"); + + UnitVoice voice2 = allocator.allocate(tag2); + assertTrue((voice2 != null), "voice should be non-null"); + assertTrue((voice2 != voice1), "new voice "); + + UnitVoice voice = allocator.allocate(tag1); + assertTrue((voice == voice1), "should be voice1 again "); + + voice = allocator.allocate(tag2); + assertTrue((voice == voice2), "should be voice2 again "); + + UnitVoice voice3 = allocator.allocate(tag3); + @SuppressWarnings("unused") + UnitVoice voice4 = allocator.allocate(tag4); + + UnitVoice voice5 = allocator.allocate(tag5); + assertTrue((voice5 == voice1), "ran out so get voice1 as oldest"); + + voice = allocator.allocate(tag2); + assertTrue((voice == voice2), "should be voice2 again "); + + // Now voice 3 should be the oldest cuz voice 2 was touched. + UnitVoice voice6 = allocator.allocate(tag6); + assertTrue((voice6 == voice3), "ran out so get voice3 as oldest"); + } + + @Test + public void testOff() { + int tag1 = 61; + int tag2 = 62; + int tag3 = 63; + int tag4 = 64; + int tag5 = 65; + int tag6 = 66; + UnitVoice voice1 = allocator.allocate(tag1); + UnitVoice voice2 = allocator.allocate(tag2); + UnitVoice voice3 = allocator.allocate(tag3); + UnitVoice voice4 = allocator.allocate(tag4); + + assertTrue(allocator.isOn(tag3), "voice 3 should start on"); + allocator.off(tag3); + assertFalse(allocator.isOn(tag3), "voice 3 should now be off"); + + allocator.off(tag2); + + UnitVoice voice5 = allocator.allocate(tag5); + assertTrue((voice5 == voice3), "should get voice3 cuz off first"); + UnitVoice voice6 = allocator.allocate(tag6); + assertTrue((voice6 == voice2), "should get voice2 cuz off second"); + voice3 = allocator.allocate(tag3); + assertTrue((voice3 == voice1), "should get voice1 cuz on first"); + + voice1 = allocator.allocate(tag1); + assertTrue((voice1 == voice4), "should get voice4 cuz next up"); + } +} diff --git a/tests/com/jsyn/SynthTestSuite.java b/tests/com/jsyn/SynthTestSuite.java deleted file mode 100644 index 958bd4f..0000000 --- a/tests/com/jsyn/SynthTestSuite.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright 2010 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; - -import junit.framework.Test; -import junit.framework.TestSuite; - -import com.jsyn.data.TestShortSample; -import com.jsyn.engine.TestDevices; -import com.jsyn.engine.TestEngine; -import com.jsyn.engine.TestFifo; -import com.jsyn.engine.TestWaveFileReadWrite; -import com.jsyn.ports.TestQueuedDataPort; -import com.jsyn.ports.TestSet; -import com.jsyn.unitgen.TestEnable; -import com.jsyn.unitgen.TestEnvelopeAttackDecay; -import com.jsyn.unitgen.TestEnvelopeDAHDSR; -import com.jsyn.unitgen.TestFunction; -import com.jsyn.unitgen.TestRamps; -import com.jsyn.util.TestFFT; -import com.jsyn.util.TestVoiceAllocator; - -/** - * Test new pure Java JSyn API. - * - * @author Phil Burk (C) 2010 Mobileer Inc - */ -public class SynthTestSuite { - - public static Test suite() { - - TestSuite suite = new TestSuite(); - - suite.addTestSuite(TestEngine.class); - suite.addTestSuite(TestSet.class); - // suite.addTestSuite( TestMath.class ); - suite.addTestSuite(TestRamps.class); - suite.addTestSuite(TestEnvelopeAttackDecay.class); - suite.addTestSuite(TestEnvelopeDAHDSR.class); - suite.addTestSuite(TestShortSample.class); - suite.addTestSuite(TestDevices.class); - suite.addTestSuite(TestQueuedDataPort.class); - suite.addTestSuite(TestFifo.class); - suite.addTestSuite(TestEnable.class); - suite.addTestSuite(TestFunction.class); - suite.addTestSuite(TestFFT.class); - // suite.addTestSuite( TestSampleLoader.class ); - suite.addTestSuite(TestWaveFileReadWrite.class); - suite.addTestSuite(TestVoiceAllocator.class); - // suite.addTestSuite(TestWrapAroundBug.class); - - return suite; - } - - /** - * Runs the test suite using the textual runner. - */ - public static void main(String[] args) { - junit.textui.TestRunner.run(suite()); - System.exit(0); - } -} diff --git a/tests/com/jsyn/benchmarks/BenchJSyn.java b/tests/com/jsyn/benchmarks/BenchJSyn.java deleted file mode 100644 index 5b8d128..0000000 --- a/tests/com/jsyn/benchmarks/BenchJSyn.java +++ /dev/null @@ -1,228 +0,0 @@ -/* - * Copyright 2013 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.benchmarks; - -import com.jsyn.JSyn; -import com.jsyn.Synthesizer; -import com.jsyn.unitgen.PassThrough; -import com.jsyn.unitgen.PitchDetector; -import com.jsyn.unitgen.SawtoothOscillator; -import com.jsyn.unitgen.SawtoothOscillatorBL; -import com.jsyn.unitgen.SawtoothOscillatorDPW; -import com.jsyn.unitgen.SineOscillator; -import com.jsyn.unitgen.SquareOscillator; -import com.jsyn.unitgen.SquareOscillatorBL; -import com.jsyn.unitgen.UnitOscillator; -import com.softsynth.math.FourierMath; - -/** - * @author Phil Burk (C) 2013 Mobileer Inc - */ -public class BenchJSyn { - private Synthesizer synth; - private long startTime; - private long endTime; - private PassThrough pass; - - public void run() { - try { - // Run multiple times to see if HotSpot compiler or cache makes a difference. - for (int i = 0; i < 4; i++) { - benchmark(); - } - } catch (InstantiationException e) { - e.printStackTrace(); - } catch (IllegalAccessException e) { - e.printStackTrace(); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - - private void benchmark() throws InstantiationException, IllegalAccessException, - InterruptedException { - double realTime = 10.0; - int count = 40; - - // benchFFTDouble(); - // benchFFTFloat(); - /* - * realTime = 20.0; benchmarkOscillator(SawtoothOscillator.class, count, realTime); - * benchmarkOscillator(SawtoothOscillatorDPW.class, count, realTime); - * benchmarkOscillator(SawtoothOscillatorBL.class, count, realTime); - */ - benchmarkOscillator(SquareOscillator.class, count, realTime); - benchmarkOscillator(SquareOscillatorBL.class, count, realTime); - - benchmarkOscillator(SineOscillator.class, count, realTime); - benchmarkPitchDetector(count, realTime); - - } - - public void benchFFTDouble() { - int size = 2048; - int bin = 5; - int count = 20000; - double[] ar = new double[size]; - double[] ai = new double[size]; - double[] magnitudes = new double[size]; - - double amplitude = 1.0; - addSineWave(size, bin, ar, amplitude); - System.out.println("Bench double FFT"); - startTiming(); - for (int i = 0; i < count; i++) { - FourierMath.transform(1, size, ar, ai); - } - - endTiming(FourierMath.class, count, size / (2.0 * 44100)); - FourierMath.calculateMagnitudes(ar, ai, magnitudes); - - assert (magnitudes[bin - 1] < 0.001); - assert (magnitudes[bin] > 0.5); - assert (magnitudes[bin + 1] < 0.001); - - } - - public void benchFFTFloat() { - int size = 2048; - int bin = 5; - int count = 20000; - float[] ar = new float[size]; - float[] ai = new float[size]; - float[] magnitudes = new float[size]; - - float amplitude = 1.0f; - addSineWave(size, bin, ar, amplitude); - - System.out.println("Bench float FFT"); - startTiming(); - for (int i = 0; i < count; i++) { - FourierMath.transform(1, size, ar, ai); - } - - endTiming(FourierMath.class, count, size / (2.0 * 44100)); - FourierMath.calculateMagnitudes(ar, ai, magnitudes); - - assert (magnitudes[bin - 1] < 0.001); - assert (magnitudes[bin] > 0.5); - assert (magnitudes[bin + 1] < 0.001); - - } - - private void addSineWave(int size, int bin, double[] ar, double amplitude) { - double phase = 0.0; - double phaseIncrement = 2.0 * Math.PI * bin / size; - for (int i = 0; i < size; i++) { - ar[i] += Math.sin(phase) * amplitude; - // System.out.println( i + " = " + ar[i] ); - phase += phaseIncrement; - } - } - - private void addSineWave(int size, int bin, float[] ar, float amplitude) { - float phase = 0.0f; - float phaseIncrement = (float) (2.0 * Math.PI * bin / size); - for (int i = 0; i < size; i++) { - ar[i] += (float) Math.sin(phase) * amplitude; - // System.out.println( i + " = " + ar[i] ); - phase += phaseIncrement; - } - } - - private void stopSynth() { - synth.stop(); - } - - private void startSynth() { - synth = JSyn.createSynthesizer(); // Mac - // synth = JSyn.createSynthesizer( new JSynAndroidAudioDevice() ); // Android - synth.setRealTime(false); - pass = new PassThrough(); - synth.add(pass); - synth.start(); - pass.start(); - } - - private void benchmarkOscillator(Class clazz, int count, double realTime) - throws InstantiationException, IllegalAccessException, InterruptedException { - startSynth(); - for (int i = 0; i < count; i++) { - UnitOscillator osc = (UnitOscillator) clazz.newInstance(); - osc.output.connect(pass.input); - synth.add(osc); - } - startTiming(); - synth.sleepFor(realTime); - endTiming(clazz, count, realTime); - stopSynth(); - } - - private void benchmarkPitchDetector(int count, double realTime) throws InstantiationException, - IllegalAccessException, InterruptedException { - startSynth(); - - PitchDetector detector = new PitchDetector(); - synth.add(detector); - double frequency = 198.0; - double period = synth.getFrameRate() / frequency; - // simple harmonic synthesis - for (int i = 0; i < count; i++) { - SineOscillator osc = new SineOscillator(); - synth.add(osc); - osc.frequency.set(frequency * (i + 1)); - osc.amplitude.set(0.5 * (1.0 - (i * 0.2))); - osc.output.connect(detector.input); - } - detector.start(); - startTiming(); - synth.sleepFor(realTime); - endTiming(PitchDetector.class, count, realTime); - - double measuredPeriod = detector.period.getValue(); - double confidence = detector.confidence.getValue(); - System.out.println("period = " + period + ", measured = " + measuredPeriod - + ", confidence = " + confidence); - if (confidence > 0.1) { - assert (Math.abs(measuredPeriod - period) < 0.1); - } - stopSynth(); - } - - private void endTiming(Class clazz, int count, double realTime) { - endTime = System.nanoTime(); - double elapsedTime = (endTime - startTime) * 1E-9; - double percent = 100.0 * elapsedTime / (realTime * count); - System.out.printf("%32s took %5.3f/%d seconds to process %5.4f of audio = %6.3f%c.\n", - clazz.getSimpleName(), elapsedTime, count, realTime, percent, '%'); - } - - private void startTiming() { - startTime = System.nanoTime(); - } - - /** - * @param args - */ - public static void main(String[] args) { - new BenchJSyn().run(); - } - -} diff --git a/tests/com/jsyn/data/TestShortSample.java b/tests/com/jsyn/data/TestShortSample.java deleted file mode 100644 index 3837c19..0000000 --- a/tests/com/jsyn/data/TestShortSample.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * 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.data; - -import junit.framework.TestCase; - -/** - * @author Phil Burk, (C) 2009 Mobileer Inc - */ -public class TestShortSample extends TestCase { - - @Override - protected void setUp() throws Exception { - super.setUp(); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - } - - public void testBytes() { - byte[] bar = { - 18, -3 - }; - short s = (short) ((bar[0] << 8) | (bar[1] & 0xFF)); - assertEquals("A", 0x12FD, s); - } - - public void testReadWrite() { - short[] data = { - 123, 456, -789, 111, 20000, -32768, 32767, 0, 9876 - }; - ShortSample sample = new ShortSample(data.length, 1); - assertEquals("Sample numFrames", data.length, sample.getNumFrames()); - - // Write and read entire sample. - sample.write(data); - short[] buffer = new short[data.length]; - sample.read(buffer); - - for (int i = 0; i < data.length; i++) { - assertEquals("read = write", data[i], buffer[i]); - } - - // Write and read part of an array. - short[] partial = { - 333, 444, 555, 666, 777 - }; - - sample.write(2, partial, 1, 3); - sample.read(1, buffer, 1, 5); - - for (int i = 0; i < data.length; i++) { - if ((i >= 2) && (i <= 4)) { - assertEquals("partial", partial[i - 1], buffer[i]); - } else { - assertEquals("read = write", data[i], buffer[i]); - } - } - - } - -} diff --git a/tests/com/jsyn/engine/TestAudioOutput.java b/tests/com/jsyn/engine/TestAudioOutput.java deleted file mode 100644 index a95d426..0000000 --- a/tests/com/jsyn/engine/TestAudioOutput.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * 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.engine; - -import java.io.IOException; - -import junit.framework.TestCase; - -import com.jsyn.devices.AudioDeviceManager; -import com.jsyn.devices.AudioDeviceOutputStream; -import com.jsyn.devices.javasound.JavaSoundAudioDevice; - -/** - * @author Phil Burk, (C) 2009 Mobileer Inc - */ -public class TestAudioOutput extends TestCase { - - SynthesisEngine synthesisEngine; - - @Override - protected void setUp() throws Exception { - super.setUp(); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - } - - public void testMonoSine() throws IOException { - System.out.println("Test mono output."); - final int FRAMES_PER_BUFFER = 128; - final int SAMPLES_PER_FRAME = 1; - double[] buffer = new double[FRAMES_PER_BUFFER * SAMPLES_PER_FRAME]; - AudioDeviceManager audioDevice = new JavaSoundAudioDevice(); - AudioDeviceOutputStream audioOutput = audioDevice.createOutputStream( - audioDevice.getDefaultOutputDeviceID(), 44100, SAMPLES_PER_FRAME); - for (int i = 0; i < FRAMES_PER_BUFFER; i++) { - double angle = (i * Math.PI * 2.0) / FRAMES_PER_BUFFER; - buffer[i] = Math.sin(angle); - } - audioOutput.start(); - for (int i = 0; i < 1000; i++) { - audioOutput.write(buffer); - } - audioOutput.stop(); - - } - - public void testStereoSine() throws IOException { - System.out.println("Test stereo output."); - final int FRAMES_PER_BUFFER = 128; - final int SAMPLES_PER_FRAME = 2; - double[] buffer = new double[FRAMES_PER_BUFFER * SAMPLES_PER_FRAME]; - AudioDeviceManager audioDevice = new JavaSoundAudioDevice(); - AudioDeviceOutputStream audioOutput = audioDevice.createOutputStream( - audioDevice.getDefaultOutputDeviceID(), 44100, SAMPLES_PER_FRAME); - int bi = 0; - for (int i = 0; i < FRAMES_PER_BUFFER; i++) { - double angle = (i * Math.PI * 2.0) / FRAMES_PER_BUFFER; - buffer[bi++] = Math.sin(angle); - buffer[bi++] = Math.sin(angle); - } - audioOutput.start(); - for (int i = 0; i < 1000; i++) { - audioOutput.write(buffer); - } - audioOutput.stop(); - - } - -} diff --git a/tests/com/jsyn/engine/TestDevices.java b/tests/com/jsyn/engine/TestDevices.java deleted file mode 100644 index 56f2c1f..0000000 --- a/tests/com/jsyn/engine/TestDevices.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * 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.engine; - -import junit.framework.TestCase; - -import com.jsyn.JSyn; -import com.jsyn.Synthesizer; -import com.jsyn.devices.AudioDeviceFactory; -import com.jsyn.devices.AudioDeviceManager; -import com.jsyn.unitgen.LineIn; -import com.jsyn.unitgen.LineOut; - -public class TestDevices extends TestCase { - // Test audio input and output simultaneously. - public void testPassThrough() { - Synthesizer synth; - LineIn lineIn; - LineOut lineOut; - // Create a context for the synthesizer. - synth = JSyn.createSynthesizer(AudioDeviceFactory.createAudioDeviceManager(true)); - // Add an audio input. - synth.add(lineIn = new LineIn()); - // Add an audio output. - synth.add(lineOut = new LineOut()); - // Connect the input to the output. - lineIn.output.connect(0, lineOut.input, 0); - lineIn.output.connect(1, lineOut.input, 1); - - // Both stereo. - int numInputChannels = 2; - int numOutputChannels = 2; - synth.start(44100, AudioDeviceManager.USE_DEFAULT_DEVICE, numInputChannels, - AudioDeviceManager.USE_DEFAULT_DEVICE, numOutputChannels); - - // We only need to start the LineOut. It will pull data from the LineIn. - lineOut.start(); - System.out.println("Audio passthrough started."); - // Sleep a while. - double sleepTime = 2.0; - try { - double time = synth.getCurrentTime(); - // Sleep for a few seconds. - synth.sleepUntil(time + sleepTime); - } catch (InterruptedException e) { - e.printStackTrace(); - } - double synthTime = synth.getCurrentTime(); - assertEquals("Time has advanced. " + synthTime, sleepTime, synthTime, 0.2); - // Stop everything. - synth.stop(); - System.out.println("All done."); - - } -} diff --git a/tests/com/jsyn/engine/TestEngine.java b/tests/com/jsyn/engine/TestEngine.java deleted file mode 100644 index 9572f3d..0000000 --- a/tests/com/jsyn/engine/TestEngine.java +++ /dev/null @@ -1,213 +0,0 @@ -/* - * 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.engine; - -import junit.framework.TestCase; - -import com.jsyn.unitgen.Add; -import com.jsyn.unitgen.LineOut; -import com.jsyn.unitgen.PitchDetector; -import com.jsyn.unitgen.SineOscillator; -import com.jsyn.unitgen.ZeroCrossingCounter; - -public class TestEngine extends TestCase { - - public void testInitialization() { - final int DEFAULT_FRAME_RATE = 44100; - SynthesisEngine synthesisEngine = new SynthesisEngine(); - assertEquals("frameCount zero before starting", 0, synthesisEngine.getFrameCount()); - assertEquals("default frameRate", DEFAULT_FRAME_RATE, synthesisEngine.getFrameRate()); - assertEquals("default pullData", true, synthesisEngine.isPullDataEnabled()); - } - - public void checkPullData(boolean pullData) { - SynthesisEngine synthesisEngine = new SynthesisEngine(); - assertEquals("default realTime", true, synthesisEngine.isRealTime()); - synthesisEngine.setRealTime(false); - - assertEquals("default pullData", true, synthesisEngine.isPullDataEnabled()); - synthesisEngine.setPullDataEnabled(pullData); - - SineOscillator sineOscillator = new SineOscillator(); - synthesisEngine.add(sineOscillator); - - LineOut lineOut = new LineOut(); - synthesisEngine.add(lineOut); - sineOscillator.output.connect(0, lineOut.input, 0); - - assertEquals("initial sine value", 0.0, sineOscillator.output.getValue()); - - synthesisEngine.start(); - if (!pullData) { - sineOscillator.start(); - } - // We always have to start the LineOut. - lineOut.start(); - synthesisEngine.generateNextBuffer(); - synthesisEngine.generateNextBuffer(); - - double value = sineOscillator.output.getValue(); - assertTrue("sine value after generation = " + value, (value > 0.0)); - } - - public void testPullDataFalse() { - checkPullData(false); - } - - public void testPullDataTrue() { - checkPullData(true); - } - - public void testMixedAdding() { - boolean gotCaught = false; - SynthesisEngine synthesisEngine1 = new SynthesisEngine(); - synthesisEngine1.setRealTime(false); - synthesisEngine1.setPullDataEnabled(true); - SynthesisEngine synthesisEngine2 = new SynthesisEngine(); - synthesisEngine2.setRealTime(false); - synthesisEngine2.setPullDataEnabled(true); - - // Create a sineOscillator but do not add it to the synth! - SineOscillator sineOscillator = new SineOscillator(); - LineOut lineOut = new LineOut(); - - synthesisEngine1.add(lineOut); - synthesisEngine2.add(sineOscillator); - try { - sineOscillator.output.connect(0, lineOut.input, 0); - } catch (RuntimeException e) { - gotCaught = true; - assertTrue("informative MPE message", e.getMessage().contains("different synths")); - } - - assertTrue("caught NPE caused by forgetting synth.add", gotCaught); - } - - public void testNotAdding() { - SynthesisEngine synthesisEngine = new SynthesisEngine(); - synthesisEngine.setRealTime(false); - synthesisEngine.setPullDataEnabled(true); - - // Create a sineOscillator but do not add it to the synth! - SineOscillator sineOscillator = new SineOscillator(); - - LineOut lineOut = new LineOut(); - sineOscillator.output.connect(0, lineOut.input, 0); - synthesisEngine.add(lineOut); - - assertEquals("initial sine value", 0.0, sineOscillator.output.getValue()); - - synthesisEngine.start(); - // We always have to start the LineOut. - lineOut.start(); - boolean gotCaught = false; - try { - synthesisEngine.generateNextBuffer(); - synthesisEngine.generateNextBuffer(); - } catch (NullPointerException e) { - gotCaught = true; - assertTrue("informative MPE message", e.getMessage().contains("forgot to add")); - } - - assertTrue("caught NPE caused by forgetting synth.add", gotCaught); - } - - public void testMultipleStarts() throws InterruptedException { - SynthesisEngine synth = new SynthesisEngine(); - - // Create a sineOscillator but do not add it to the synth! - SineOscillator osc = new SineOscillator(); - ZeroCrossingCounter counter = new ZeroCrossingCounter(); - PitchDetector pitchDetector = new PitchDetector(); - LineOut lineOut = new LineOut(); - synth.add(osc); - synth.add(counter); - synth.add(lineOut); - synth.add(pitchDetector); - osc.output.connect(counter.input); - osc.output.connect(pitchDetector.input); - counter.output.connect(0, lineOut.input, 0); - - assertEquals("initial count", 0, counter.getCount()); - - int[] rates = { - 32000, 48000, 44100, 22050 - }; - for (int rate : rates) { - synth.start(rate); - lineOut.start(); - pitchDetector.start(); - - double time = synth.getCurrentTime(); - double interval = 1.0; - time += interval; - - long previousFrameCount = counter.getCount(); - synth.sleepUntil(time); - - double frequencyMeasured = pitchDetector.frequency.get(); - double confidenceMeasured = pitchDetector.confidence.get(); - double oscFreq = osc.frequency.get(); - String msg = "freq at " + rate + " Hz"; - System.out.println(msg); - assertEquals(msg, oscFreq, frequencyMeasured, oscFreq * 0.1); - assertEquals("pitch confidence", 0.9, confidenceMeasured, 0.1); - - double expectedCount = interval * oscFreq; - double framesMeasured = counter.getCount() - previousFrameCount; - msg = "count at " + rate + " Hz"; - System.out.println(msg); - assertEquals(msg, expectedCount, framesMeasured, expectedCount * 0.1); - - synth.stop(); - } - - } - - - public void testScheduler() throws InterruptedException { - SynthesisEngine synth = new SynthesisEngine(); - synth.setRealTime(false); - Add adder = new Add(); - synth.add(adder); - synth.start(); - adder.start(); - adder.inputA.set(4.0); - adder.inputB.set(10.0); - synth.sleepFor(0.1); - assertEquals("simple add", 14.0, adder.output.get(), 0.01); - - // Schedule a set() in the near future. - double time = synth.getCurrentTime(); - adder.inputA.set(7.0, time + 1.0); - synth.sleepFor(0.5); - assertEquals("before scheduled set", 14.0, adder.output.get(), 0.01); - synth.sleepFor(1.0); - assertEquals("after scheduled set", 17.0, adder.output.get(), 0.01); - - // Schedule a set() in the near future then cancel it. - time = synth.getCurrentTime(); - adder.inputA.set(5.0, time + 1.0); - synth.sleepFor(0.5); - assertEquals("before scheduled set", 17.0, adder.output.get(), 0.01); - synth.clearCommandQueue(); - synth.sleepFor(1.0); - assertEquals("after canceled set", 17.0, adder.output.get(), 0.01); - - synth.stop(); - } -} diff --git a/tests/com/jsyn/engine/TestFifo.java b/tests/com/jsyn/engine/TestFifo.java deleted file mode 100644 index 03333bf..0000000 --- a/tests/com/jsyn/engine/TestFifo.java +++ /dev/null @@ -1,250 +0,0 @@ -/* - * 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.engine; - -import junit.framework.TestCase; - -import com.jsyn.io.AudioFifo; - -public class TestFifo extends TestCase { - - public void testBasic() { - Thread watchdog = startWatchdog(600); - - AudioFifo fifo = new AudioFifo(); - fifo.setReadWaitEnabled(false); - fifo.allocate(8); - assertEquals("start empty", 0, fifo.available()); - - assertEquals("read back Nan when emopty", Double.NaN, fifo.read()); - - fifo.write(1.0); - assertEquals("added one value", 1, fifo.available()); - assertEquals("read back same value", 1.0, fifo.read()); - assertEquals("back to empty", 0, fifo.available()); - - for (int i = 0; i < fifo.size(); i++) { - assertEquals("adding data", i, fifo.available()); - fifo.write(100.0 + i); - } - for (int i = 0; i < fifo.size(); i++) { - assertEquals("removing data", fifo.size() - i, fifo.available()); - assertEquals("reading back data", 100.0 + i, fifo.read()); - } - watchdog.interrupt(); - } - - /** - * Wrap around several times to test masking. - */ - public void testWrapping() { - - final int chunk = 5; - AudioFifo fifo = new AudioFifo(); - fifo.allocate(8); - double value = 1000.0; - for (int i = 0; i < (fifo.size() * chunk); i++) { - value = checkFifoChunk(fifo, value, chunk); - } - - } - - private double checkFifoChunk(AudioFifo fifo, double value, int chunk) { - for (int i = 0; i < chunk; i++) { - assertEquals("adding data", i, fifo.available()); - fifo.write(value + i); - } - for (int i = 0; i < chunk; i++) { - assertEquals("removing data", chunk - i, fifo.available()); - assertEquals("reading back data", value + i, fifo.read()); - } - return value + chunk; - } - - public void testBadSize() { - boolean caught = false; - try { - AudioFifo fifo = new AudioFifo(); - fifo.allocate(20); // not power of 2 - assertTrue("should not get here", false); - } catch (IllegalArgumentException e) { - caught = true; - } - assertTrue("should have caught size exception", caught); - } - - public void testSingleReadWait() { - final int chunk = 5; - final AudioFifo fifo = new AudioFifo(); - fifo.allocate(8); - - fifo.setWriteWaitEnabled(false); - fifo.setReadWaitEnabled(true); - final double value = 50.0; - - // Schedule a delayed write in another thread. - new Thread() { - @Override - public void run() { - try { - sleep(200); - for (int i = 0; i < chunk; i++) { - fifo.write(value + i); - } - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - }.start(); - - Thread watchdog = startWatchdog(500); - for (int i = 0; i < chunk; i++) { - assertEquals("reading back data", value + i, fifo.read()); - } - watchdog.interrupt(); - } - - private Thread startWatchdog(final int msec) { - Thread watchdog = new Thread() { - @Override - public void run() { - try { - sleep(msec); - assertTrue("test must still be waiting", false); - } catch (InterruptedException e) { - } - } - }; - watchdog.start(); - return watchdog; - } - - public void testSingleWriteWait() { - final int chunk = 13; - final AudioFifo fifo = new AudioFifo(); - fifo.allocate(8); - - fifo.setWriteWaitEnabled(true); - fifo.setReadWaitEnabled(true); - final double value = 50.0; - - // Schedule a delayed read in another thread. - Thread readThread = new Thread() { - @Override - public void run() { - try { - sleep(200); - for (int i = 0; i < chunk; i++) { - // System.out.println( "testSingleWriteWait: try to read" ); - double got = fifo.read(); - assertEquals("adding data", value + i, got); - // System.out.println( "testSingleWriteWait: read " + got ); - } - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - }; - readThread.start(); - - Thread watchdog = startWatchdog(500); - // Try to write more than will fit so we will hang. - for (int i = 0; i < chunk; i++) { - fifo.write(value + i); - } - watchdog.interrupt(); - - try { - readThread.join(200); - } catch (InterruptedException e) { - e.printStackTrace(); - } - assertEquals("readThread should be done.", false, readThread.isAlive()); - } - - public void testBlockReadWait() { - final int chunk = 50; - final AudioFifo fifo = new AudioFifo(); - fifo.allocate(8); - - fifo.setWriteWaitEnabled(false); - fifo.setReadWaitEnabled(true); - final double value = 300.0; - double[] readBuffer = new double[chunk]; - - // Schedule delayed writes in another thread. - new Thread() { - @Override - public void run() { - int numWritten = 0; - double[] writeBuffer = new double[4]; - try { - while (numWritten < chunk) { - sleep(30); - for (int i = 0; i < writeBuffer.length; i++) { - writeBuffer[i] = value + numWritten; - numWritten += 1; - } - - fifo.write(writeBuffer); - } - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - }.start(); - - Thread watchdog = startWatchdog(600); - fifo.read(readBuffer); - for (int i = 0; i < chunk; i++) { - assertEquals("reading back data", value + i, readBuffer[i]); - } - watchdog.interrupt(); - - } - - public void testBlockReadAndWriteWaitStress() { - final int chunk = 10000000; // 10 Megabytes - final AudioFifo fifo = new AudioFifo(); - fifo.allocate(8); - - fifo.setWriteWaitEnabled(true); - fifo.setReadWaitEnabled(true); - final double value = 50.0; - - // Schedule a delayed write in another thread. - new Thread() { - @Override - public void run() { - try { - sleep(200); - for (int i = 0; i < chunk; i++) { - fifo.write(value + i); - } - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - }.start(); - - Thread watchdog = startWatchdog(10000); - for (int i = 0; i < chunk; i++) { - assertEquals("reading back data", value + i, fifo.read()); - } - watchdog.interrupt(); - } -} diff --git a/tests/com/jsyn/engine/TestWaveFileReadWrite.java b/tests/com/jsyn/engine/TestWaveFileReadWrite.java deleted file mode 100644 index ee406de..0000000 --- a/tests/com/jsyn/engine/TestWaveFileReadWrite.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * 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.engine; - -import java.io.File; -import java.io.IOException; - -import javax.sound.sampled.UnsupportedAudioFileException; - -import junit.framework.TestCase; - -import com.jsyn.data.FloatSample; -import com.jsyn.util.SampleLoader; -import com.jsyn.util.WaveFileWriter; - -public class TestWaveFileReadWrite extends TestCase { - - public void checkWriteReadWave(int numChannels, float[] data) throws IOException, - UnsupportedAudioFileException { - File temp = File.createTempFile("test_wave", ".wav"); - temp.deleteOnExit(); - System.out.println("Creating wave file " + temp); - - WaveFileWriter writer = new WaveFileWriter(temp); - writer.setFrameRate(44100); - writer.setSamplesPerFrame(numChannels); - writer.setBitsPerSample(16); - - for (int i = 0; i < data.length; i++) { - writer.write(data[i]); - } - writer.close(); - - // TODO Make sure blow up if writing after close. - // writer.write( 0.7 ); - - FloatSample sample = SampleLoader.loadFloatSample(temp); - assertEquals("stereo", numChannels, sample.getChannelsPerFrame()); - assertEquals("frame rate", 44100.0, sample.getFrameRate()); - - for (int i = 0; i < data.length; i++) { - float v = data[i]; - if (v > 1.0) - v = 1.0f; - else if (v < -1.0) - v = -1.0f; - assertEquals("sample data", v, sample.readDouble(i), 0.0001); - } - - } - - public void testRamp() throws IOException, UnsupportedAudioFileException { - float[] data = new float[200]; - for (int i = 0; i < data.length; i++) { - data[i] = i / 1000.0f; - } - - checkWriteReadWave(2, data); - } - - public void testClippedSine() throws IOException, UnsupportedAudioFileException { - float[] data = new float[200]; - for (int i = 0; i < data.length; i++) { - double phase = i * Math.PI * 2.0 / 100; - data[i] = (float) (1.3 * Math.sin(phase)); - } - - checkWriteReadWave(2, data); - } - - public void testArguments() throws IOException { - File temp = File.createTempFile("test_wave", ".wav"); - temp.deleteOnExit(); - System.out.println("Creating wave file " + temp); - - WaveFileWriter writer = new WaveFileWriter(temp); - writer.setBitsPerSample(16); - assertEquals("bitsPerSample", 16, writer.getBitsPerSample()); - writer.setBitsPerSample(24); - assertEquals("bitsPerSample", 24, writer.getBitsPerSample()); - boolean caughtIt = false; - try { - writer.setBitsPerSample(17); - assertTrue("tried setting illegal value", false); - } catch (IllegalArgumentException e) { - // e.printStackTrace(); - caughtIt = true; - } - assertTrue("17 generated exception", caughtIt); - writer.close(); - } - -} diff --git a/tests/com/jsyn/examples/AudioPassThrough.java b/tests/com/jsyn/examples/AudioPassThrough.java deleted file mode 100644 index fb46992..0000000 --- a/tests/com/jsyn/examples/AudioPassThrough.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2010 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.examples; - -import com.jsyn.JSyn; -import com.jsyn.Synthesizer; -import com.jsyn.devices.AudioDeviceManager; -import com.jsyn.unitgen.LineIn; -import com.jsyn.unitgen.LineOut; - -/** - * Pass audio input to audio output. - * - * @author Phil Burk (C) 2010 Mobileer Inc - */ -public class AudioPassThrough { - Synthesizer synth; - LineIn lineIn; - LineOut lineOut; - - private void test() { - // Create a context for the synthesizer. - synth = JSyn.createSynthesizer(); - // Add an audio input. - synth.add(lineIn = new LineIn()); - // Add an audio output. - synth.add(lineOut = new LineOut()); - // Connect the input to the output. - lineIn.output.connect(0, lineOut.input, 0); - lineIn.output.connect(1, lineOut.input, 1); - - // Both stereo. - int numInputChannels = 2; - int numOutputChannels = 2; - synth.start(44100, AudioDeviceManager.USE_DEFAULT_DEVICE, numInputChannels, - AudioDeviceManager.USE_DEFAULT_DEVICE, numOutputChannels); - - // We only need to start the LineOut. It will pull data from the LineIn. - lineOut.start(); - System.out.println("Audio passthrough started."); - // Sleep a while. - try { - double time = synth.getCurrentTime(); - // Sleep for a few seconds. - synth.sleepUntil(time + 8.0); - } catch (InterruptedException e) { - e.printStackTrace(); - } - // Stop everything. - synth.stop(); - System.out.println("All done."); - } - - public static void main(String[] args) { - new AudioPassThrough().test(); - } -} diff --git a/tests/com/jsyn/examples/ChebyshevSong.java b/tests/com/jsyn/examples/ChebyshevSong.java deleted file mode 100644 index fffd4bb..0000000 --- a/tests/com/jsyn/examples/ChebyshevSong.java +++ /dev/null @@ -1,181 +0,0 @@ -/* - * Copyright 2010 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.examples; - -import java.awt.BorderLayout; - -import javax.swing.JApplet; - -import com.jsyn.JSyn; -import com.jsyn.Synthesizer; -import com.jsyn.instruments.WaveShapingVoice; -import com.jsyn.scope.AudioScope; -import com.jsyn.swing.JAppletFrame; -import com.jsyn.unitgen.Add; -import com.jsyn.unitgen.LineOut; -import com.jsyn.util.PseudoRandom; -import com.jsyn.util.VoiceAllocator; -import com.softsynth.math.AudioMath; -import com.softsynth.shared.time.TimeStamp; - -/*************************************************************** - * Play notes using a WaveShapingVoice. Allocate the notes using a VoiceAllocator. - * - * @author Phil Burk (C) 2010 Mobileer Inc - */ -public class ChebyshevSong extends JApplet implements Runnable { - private static final long serialVersionUID = -7459137388629333223L; - private Synthesizer synth; - private Add mixer; - private LineOut lineOut; - private AudioScope scope; - private volatile boolean go = false; - private PseudoRandom pseudo = new PseudoRandom(); - private final static int MAX_VOICES = 8; - private final static int MAX_NOTES = 5; - private VoiceAllocator allocator; - private final static int scale[] = { - 0, 2, 4, 7, 9 - }; // pentatonic scale - - /* Can be run as either an application or as an applet. */ - public static void main(String args[]) { - ChebyshevSong applet = new ChebyshevSong(); - JAppletFrame frame = new JAppletFrame("ChebyshevSong", applet); - frame.setSize(640, 300); - frame.setVisible(true); - frame.test(); - } - - /* - * Setup synthesis. - */ - @Override - public void start() { - setLayout(new BorderLayout()); - - synth = JSyn.createSynthesizer(); - - // Use a submix so we can show it on the scope. - synth.add(mixer = new Add()); - synth.add(lineOut = new LineOut()); - - mixer.output.connect(0, lineOut.input, 0); - mixer.output.connect(0, lineOut.input, 1); - - WaveShapingVoice[] voices = new WaveShapingVoice[MAX_VOICES]; - for (int i = 0; i < MAX_VOICES; i++) { - WaveShapingVoice voice = new WaveShapingVoice(); - synth.add(voice); - voice.usePreset(0); - voice.getOutput().connect(mixer.inputA); - voices[i] = voice; - } - allocator = new VoiceAllocator(voices); - - // Start synthesizer using default stereo output at 44100 Hz. - synth.start(); - lineOut.start(); - - // Use a scope to show the mixed output. - scope = new AudioScope(synth); - scope.addProbe(mixer.output); - scope.setTriggerMode(AudioScope.TriggerMode.NORMAL); - scope.getView().setControlsVisible(false); - add(BorderLayout.CENTER, scope.getView()); - scope.start(); - - /* Synchronize Java display. */ - getParent().validate(); - getToolkit().sync(); - - // start thread that plays notes - Thread thread = new Thread(this); - go = true; - thread.start(); - - } - - @Override - public void stop() { - // tell song thread to finish - go = false; - removeAll(); - synth.stop(); - } - - double indexToFrequency(int index) { - int octave = index / scale.length; - int temp = index % scale.length; - int pitch = scale[temp] + (12 * octave); - return AudioMath.pitchToFrequency(pitch + 16); - } - - private void noteOff(double time, int noteNumber) { - allocator.noteOff(noteNumber, new TimeStamp(time)); - } - - private void noteOn(double time, int noteNumber) { - double frequency = indexToFrequency(noteNumber); - double amplitude = 0.1; - TimeStamp timeStamp = new TimeStamp(time); - allocator.noteOn(noteNumber, frequency, amplitude, timeStamp); - allocator.setPort(noteNumber, "Range", 0.7, synth.createTimeStamp()); - } - - @Override - public void run() { - // always choose a new song based on time&date - int savedSeed = (int) System.currentTimeMillis(); - // calculate tempo - double duration = 0.2; - // set time ahead of any system latency - double advanceTime = 0.5; - // time for next note to start - double nextTime = synth.getCurrentTime() + advanceTime; - // note is ON for half the duration - double onTime = duration / 2; - int beatIndex = 0; - try { - do { - // on every measure, maybe repeat previous pattern - if ((beatIndex & 7) == 0) { - if ((Math.random() < (1.0 / 2.0))) - pseudo.setSeed(savedSeed); - else if ((Math.random() < (1.0 / 2.0))) - savedSeed = pseudo.getSeed(); - } - - // Play a bunch of random notes in the scale. - int numNotes = pseudo.choose(MAX_NOTES); - for (int i = 0; i < numNotes; i++) { - int noteNumber = pseudo.choose(30); - noteOn(nextTime, noteNumber); - noteOff(nextTime + onTime, noteNumber); - } - - nextTime += duration; - beatIndex += 1; - - // wake up before we need to play note to cover system latency - synth.sleepUntil(nextTime - advanceTime); - } while (go); - } catch (InterruptedException e) { - System.err.println("Song exiting. " + e); - } - } -} diff --git a/tests/com/jsyn/examples/CircuitTester.java b/tests/com/jsyn/examples/CircuitTester.java deleted file mode 100644 index 948e8a0..0000000 --- a/tests/com/jsyn/examples/CircuitTester.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright 2012 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.examples; - -import java.awt.BorderLayout; - -import javax.swing.JApplet; - -import com.jsyn.JSyn; -import com.jsyn.Synthesizer; -import com.jsyn.instruments.DualOscillatorSynthVoice; -import com.jsyn.instruments.SubtractiveSynthVoice; -import com.jsyn.scope.AudioScope; -import com.jsyn.swing.JAppletFrame; -import com.jsyn.swing.SoundTweaker; -import com.jsyn.unitgen.LineOut; -import com.jsyn.unitgen.UnitSource; - -/** - * Listen to a circuit while tweaking it knobs. Show output in a scope. - * - * @author Phil Burk (C) 2012 Mobileer Inc - */ -public class CircuitTester extends JApplet { - private static final long serialVersionUID = -2704222221111608377L; - private Synthesizer synth; - private LineOut lineOut; - private SoundTweaker tweaker; - private UnitSource unitSource; - private AudioScope scope; - - @Override - public void init() { - setLayout(new BorderLayout()); - - synth = JSyn.createSynthesizer(); - synth.add(lineOut = new LineOut()); - - unitSource = createUnitSource(); - synth.add(unitSource.getUnitGenerator()); - - // Connect the source to both left and right speakers. - unitSource.getOutput().connect(0, lineOut.input, 0); - unitSource.getOutput().connect(0, lineOut.input, 1); - - tweaker = new SoundTweaker(synth, unitSource.getUnitGenerator().getClass().getName(), - unitSource); - add(tweaker, BorderLayout.CENTER); - - // Use a scope to see the output. - scope = new AudioScope(synth); - scope.addProbe(unitSource.getOutput()); - scope.setTriggerMode(AudioScope.TriggerMode.AUTO); - scope.getView().setControlsVisible(false); - add(BorderLayout.SOUTH, scope.getView()); - - validate(); - } - - /** - * Override this to test your own circuits. - * - * @return - */ - public UnitSource createUnitSource() { - //return new SampleHoldNoteBlaster(); - //return new com.syntona.exported.FMVoice(); - return new DualOscillatorSynthVoice(); - //return new WindCircuit(); - //return new WhiteNoise(); - //return new BrownNoise(); - } - - @Override - public void start() { - // Start synthesizer using default stereo output at 44100 Hz. - synth.start(); - // Start the LineOut. It will pull data from the other units. - lineOut.start(); - - scope.start(); - } - - @Override - public void stop() { - scope.stop(); - synth.stop(); - } - - /* Can be run as either an application or as an applet. */ - public static void main(String args[]) { - CircuitTester applet = new CircuitTester(); - JAppletFrame frame = new JAppletFrame("JSyn Circuit Tester", applet); - frame.setSize(600, 600); - frame.setVisible(true); - frame.test(); - } - -} diff --git a/tests/com/jsyn/examples/CustomCubeUnit.java b/tests/com/jsyn/examples/CustomCubeUnit.java deleted file mode 100644 index 892c30c..0000000 --- a/tests/com/jsyn/examples/CustomCubeUnit.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2010 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.examples; - -import com.jsyn.unitgen.UnitFilter; - -/** - * Custom unit generator that can be used with other JSyn units. Cube the input value and write it - * to output port. - * - * @author Phil Burk (C) 2010 Mobileer Inc - */ -public class CustomCubeUnit extends UnitFilter { - - @Override - /** This is where the synthesis occurs. - * It is called in a high priority background thread so do not do - * anything crazy here like reading a file or doing network I/O. - * Just do fast arithmetic. - *
- * The start and limit allow us to do either block or single sample processing. - */ - public void generate(int start, int limit) { - // Get signal arrays from ports. - double[] inputs = input.getValues(); - double[] outputs = output.getValues(); - - for (int i = start; i < limit; i++) { - double x = inputs[i]; - // Do the math. - outputs[i] = x * x * x; - } - } -} diff --git a/tests/com/jsyn/examples/DualOscilloscope.java b/tests/com/jsyn/examples/DualOscilloscope.java deleted file mode 100644 index d7798f4..0000000 --- a/tests/com/jsyn/examples/DualOscilloscope.java +++ /dev/null @@ -1,164 +0,0 @@ -/* - * Copyright 2012 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.examples; - -import java.awt.BorderLayout; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.util.ArrayList; - -import javax.swing.JApplet; -import javax.swing.JComboBox; - -import com.jsyn.JSyn; -import com.jsyn.Synthesizer; -import com.jsyn.devices.AudioDeviceFactory; -import com.jsyn.devices.AudioDeviceManager; -import com.jsyn.scope.AudioScope; -import com.jsyn.swing.JAppletFrame; -import com.jsyn.unitgen.ChannelIn; -import com.jsyn.unitgen.PassThrough; - -/** - * Two channel oscilloscope that demonstrates the use of audio input. - * - * @author Phil Burk (C) 2012 Mobileer Inc - */ -public class DualOscilloscope extends JApplet { - private static final long serialVersionUID = -2704222221111608377L; - private Synthesizer synth; - private ChannelIn channel1; - private ChannelIn channel2; - private PassThrough pass1; - private PassThrough pass2; - private AudioScope scope; - private AudioDeviceManager audioManager; - private int defaultInputId; - private ArrayList deviceNames = new ArrayList(); - private ArrayList deviceMaxInputs = new ArrayList(); - private ArrayList deviceIds = new ArrayList(); - private int defaultSelection; - private JComboBox deviceComboBox; - - @Override - public void init() { - audioManager = AudioDeviceFactory.createAudioDeviceManager(true); - synth = JSyn.createSynthesizer(audioManager); - - int numDevices = audioManager.getDeviceCount(); - defaultInputId = audioManager.getDefaultInputDeviceID(); - for (int i = 0; i < numDevices; i++) { - int maxInputs = audioManager.getMaxInputChannels(i); - if (maxInputs > 0) { - String deviceName = audioManager.getDeviceName(i); - String itemName = maxInputs + ", " + deviceName + " (#" + i + ")"; - if (i == defaultInputId) { - defaultSelection = deviceNames.size(); - itemName += " (Default)"; - } - deviceNames.add(itemName); - deviceMaxInputs.add(maxInputs); - deviceIds.add(i); - } - } - - synth.add(channel1 = new ChannelIn()); - channel1.setChannelIndex(0); - synth.add(channel2 = new ChannelIn()); - channel2.setChannelIndex(1); - - // Use PassThrough so we can easily disconnect input channels from the scope. - synth.add(pass1 = new PassThrough()); - synth.add(pass2 = new PassThrough()); - - setupGUI(); - } - - private void setupGUI() { - setLayout(new BorderLayout()); - - deviceComboBox = new JComboBox(deviceNames.toArray(new String[0])); - deviceComboBox.setSelectedIndex(defaultSelection); - add(deviceComboBox, BorderLayout.NORTH); - deviceComboBox.addActionListener(new ActionListener() { - - @Override - public void actionPerformed(ActionEvent e) { - stopAudio(); - int itemIndex = deviceComboBox.getSelectedIndex(); - startAudio(itemIndex); - } - }); - - scope = new AudioScope(synth); - - scope.addProbe(pass1.output); - scope.addProbe(pass2.output); - - scope.setTriggerMode(AudioScope.TriggerMode.AUTO); - scope.getView().setControlsVisible(true); - add(scope.getView(), BorderLayout.CENTER); - validate(); - } - - protected void startAudio(int itemIndex) { - // Both stereo. - int numInputChannels = deviceMaxInputs.get(itemIndex); - if (numInputChannels > 2) - numInputChannels = 2; - int inputDeviceIndex = deviceIds.get(itemIndex); - synth.start(44100, inputDeviceIndex, numInputChannels, - AudioDeviceManager.USE_DEFAULT_DEVICE, 0); - - channel1.output.connect(pass1.input); - // Only connect second channel if more than one input channel. - if (numInputChannels > 1) { - channel2.output.connect(pass2.input); - } - - // We only need to start the LineOut. It will pull data from the - // channels. - scope.start(); - } - - @Override - public void start() { - startAudio(defaultSelection); - } - - public void stopAudio() { - pass1.input.disconnectAll(); - pass2.input.disconnectAll(); - scope.stop(); - synth.stop(); - } - - @Override - public void stop() { - stopAudio(); - } - - /* Can be run as either an application or as an applet. */ - public static void main(String args[]) { - DualOscilloscope applet = new DualOscilloscope(); - JAppletFrame frame = new JAppletFrame("Dual Oscilloscope", applet); - frame.setSize(640, 400); - frame.setVisible(true); - frame.test(); - } - -} diff --git a/tests/com/jsyn/examples/EditEnvelope1.java b/tests/com/jsyn/examples/EditEnvelope1.java deleted file mode 100644 index 763037b..0000000 --- a/tests/com/jsyn/examples/EditEnvelope1.java +++ /dev/null @@ -1,148 +0,0 @@ -/* - * Copyright 1997 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. - */ -/** - * Test Envelope using Java Audio Synthesizer - * Trigger attack or release portion. - * - * @author (C) 1997 Phil Burk - */ - -package com.jsyn.examples; - -import java.awt.BorderLayout; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; - -import javax.swing.JApplet; -import javax.swing.JButton; -import javax.swing.JPanel; - -import com.jsyn.JSyn; -import com.jsyn.Synthesizer; -import com.jsyn.data.SegmentedEnvelope; -import com.jsyn.swing.EnvelopeEditorPanel; -import com.jsyn.swing.EnvelopePoints; -import com.jsyn.swing.JAppletFrame; -import com.jsyn.unitgen.LineOut; -import com.jsyn.unitgen.SawtoothOscillatorBL; -import com.jsyn.unitgen.UnitOscillator; -import com.jsyn.unitgen.VariableRateDataReader; -import com.jsyn.unitgen.VariableRateMonoReader; - -public class EditEnvelope1 extends JApplet { - private Synthesizer synth; - private UnitOscillator osc; - private LineOut lineOut; - private SegmentedEnvelope envelope; - private VariableRateDataReader envelopePlayer; - - final int MAX_FRAMES = 16; - JButton hitme; - JButton attackButton; - JButton releaseButton; - private EnvelopeEditorPanel envEditor; - private EnvelopePoints points; - - /* Can be run as either an application or as an applet. */ - public static void main(String args[]) { - EditEnvelope1 applet = new EditEnvelope1(); - JAppletFrame frame = new JAppletFrame("Test SynthEnvelope", applet); - frame.setSize(440, 200); - frame.setVisible(true); - frame.test(); - } - - /* - * Setup synthesis. - */ - @Override - public void start() { - setLayout(new BorderLayout()); - - // Create a context for the synthesizer. - synth = JSyn.createSynthesizer(); - // Add a tone generator. - synth.add(osc = new SawtoothOscillatorBL()); - // Add an envelope player. - synth.add(envelopePlayer = new VariableRateMonoReader()); - - envelope = new SegmentedEnvelope(MAX_FRAMES); - - // Add an output mixer. - synth.add(lineOut = new LineOut()); - envelopePlayer.output.connect(osc.amplitude); - // Connect the oscillator to the output. - osc.output.connect(0, lineOut.input, 0); - osc.output.connect(0, lineOut.input, 1); - - // Start synthesizer using default stereo output at 44100 Hz. - synth.start(); - - JPanel bottomPanel = new JPanel(); - bottomPanel.add(hitme = new JButton("On")); - hitme.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - points.updateEnvelopeIfDirty(envelope); - envelopePlayer.dataQueue.queueOn(envelope); - } - }); - - bottomPanel.add(attackButton = new JButton("Off")); - attackButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - points.updateEnvelopeIfDirty(envelope); - envelopePlayer.dataQueue.queueOff(envelope); - } - }); - - bottomPanel.add(releaseButton = new JButton("Queue")); - releaseButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - points.updateEnvelopeIfDirty(envelope); - envelopePlayer.dataQueue.queue(envelope); - } - }); - - add(bottomPanel, BorderLayout.SOUTH); - lineOut.start(); - - // Create vector of points for editor. - points = new EnvelopePoints(); - points.setName(osc.amplitude.getName()); - - // Setup initial envelope shape. - points.add(0.5, 1.0); - points.add(0.5, 0.2); - points.add(0.5, 0.8); - points.add(0.5, 0.0); - points.updateEnvelope(envelope); - - // Add an envelope editor to the center of the panel. - add("Center", envEditor = new EnvelopeEditorPanel(points, MAX_FRAMES)); - - /* Synchronize Java display. */ - getParent().validate(); - getToolkit().sync(); - } - - @Override - public void stop() { - synth.stop(); - } -} diff --git a/tests/com/jsyn/examples/FFTPassthrough.java b/tests/com/jsyn/examples/FFTPassthrough.java deleted file mode 100644 index 1a1a7c5..0000000 --- a/tests/com/jsyn/examples/FFTPassthrough.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright 2010 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.examples; - -import com.jsyn.JSyn; -import com.jsyn.Synthesizer; -import com.jsyn.unitgen.LineOut; -import com.jsyn.unitgen.PassThrough; -import com.jsyn.unitgen.SineOscillator; -import com.jsyn.unitgen.SpectralFFT; -import com.jsyn.unitgen.SpectralIFFT; -import com.jsyn.unitgen.UnitOscillator; - -/** - * Play a sine sweep through an FFT/IFFT pair. - * - * @author Phil Burk (C) 2010 Mobileer Inc - */ -public class FFTPassthrough { - private Synthesizer synth; - private PassThrough center; - private UnitOscillator osc; - private UnitOscillator lfo; - private SpectralFFT fft; - private SpectralIFFT ifft1; - private LineOut lineOut; - private SpectralIFFT ifft2; - - private void test() { - // Create a context for the synthesizer. - synth = JSyn.createSynthesizer(); - - // Start synthesizer using default stereo output at 44100 Hz. - synth.start(); - - // Add a tone generator. - synth.add(center = new PassThrough()); - // synth.add( osc = new SawtoothOscillatorBL() ); - synth.add(osc = new SineOscillator()); - synth.add(lfo = new SineOscillator()); - synth.add(fft = new SpectralFFT()); - synth.add(ifft1 = new SpectralIFFT()); - synth.add(ifft2 = new SpectralIFFT()); - // Add a stereo audio output unit. - synth.add(lineOut = new LineOut()); - - // Connect the oscillator to both channels of the output. - center.output.connect(osc.frequency); - lfo.output.connect(osc.frequency); - osc.output.connect(fft.input); - fft.output.connect(ifft1.input); - fft.output.connect(ifft2.input); - ifft1.output.connect(0, lineOut.input, 0); - ifft2.output.connect(0, lineOut.input, 1); - - // Set the frequency and amplitude for the modulated sine wave. - center.input.set(600.0); - lfo.frequency.set(0.2); - lfo.amplitude.set(400.0); - osc.amplitude.set(0.6); - - // We only need to start the LineOut. It will pull data through the - // chain. - lineOut.start(); - - System.out.println("You should now be hearing a clean oscillator on the left channel,"); - System.out.println("and the FFT->IFFT processed signal on the right channel."); - - // Sleep while the sound is generated in the background. - try { - double time = synth.getCurrentTime(); - // Sleep for a few seconds. - synth.sleepUntil(time + 20.0); - } catch (InterruptedException e) { - e.printStackTrace(); - } - - System.out.println("Stop playing. -------------------"); - // Stop everything. - synth.stop(); - } - - public static void main(String[] args) { - new FFTPassthrough().test(); - } -} diff --git a/tests/com/jsyn/examples/GoogleWaveOscillator.java b/tests/com/jsyn/examples/GoogleWaveOscillator.java deleted file mode 100644 index 2f45cc4..0000000 --- a/tests/com/jsyn/examples/GoogleWaveOscillator.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright 2010 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.examples; - -import com.jsyn.ports.UnitInputPort; -import com.jsyn.unitgen.UnitOscillator; - -/** - * Custom unit generator to create the waveform shown on the Google home page on 2/22/12. - * - * @author Phil Burk (C) 2010 Mobileer Inc - */ -public class GoogleWaveOscillator extends UnitOscillator { - public UnitInputPort variance; - private double phaseIncrement = 0.1; - private double previousY; - private double randomAmplitude = 0.0; - - public GoogleWaveOscillator() { - addPort(variance = new UnitInputPort("Variance", 0.0)); - } - - @Override - public void generate(int start, int limit) { - // Get signal arrays from ports. - double[] freqs = frequency.getValues(); - double[] outputs = output.getValues(); - double currentPhase = phase.getValue(); - double y; - - for (int i = start; i < limit; i++) { - if (currentPhase > 0.0) { - double p = currentPhase; - y = Math.sqrt(4.0 * (p * (1.0 - p))); - } else { - double p = -currentPhase; - y = -Math.sqrt(4.0 * (p * (1.0 - p))); - } - - if ((previousY * y) <= 0.0) { - // Calculate randomly offset phaseIncrement. - double v = variance.getValues()[0]; - double range = ((Math.random() - 0.5) * 4.0 * v); - double scale = Math.pow(2.0, range); - phaseIncrement = convertFrequencyToPhaseIncrement(freqs[i]) * scale; - - // Calculate random amplitude. - scale = 1.0 + ((Math.random() - 0.5) * 1.5 * v); - randomAmplitude = amplitude.getValues()[0] * scale; - } - - outputs[i] = y * randomAmplitude; - previousY = y; - - currentPhase = incrementWrapPhase(currentPhase, phaseIncrement); - } - phase.setValue(currentPhase); - } -} diff --git a/tests/com/jsyn/examples/HearDAHDSR.java b/tests/com/jsyn/examples/HearDAHDSR.java deleted file mode 100644 index 23e6fb5..0000000 --- a/tests/com/jsyn/examples/HearDAHDSR.java +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright 2010 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.examples; - -import java.awt.GridLayout; - -import javax.swing.BorderFactory; -import javax.swing.JApplet; - -import com.jsyn.JSyn; -import com.jsyn.Synthesizer; -import com.jsyn.ports.UnitInputPort; -import com.jsyn.swing.DoubleBoundedRangeModel; -import com.jsyn.swing.JAppletFrame; -import com.jsyn.swing.PortModelFactory; -import com.jsyn.swing.RotaryTextController; -import com.jsyn.unitgen.EnvelopeDAHDSR; -import com.jsyn.unitgen.LineOut; -import com.jsyn.unitgen.SineOscillator; -import com.jsyn.unitgen.SquareOscillator; -import com.jsyn.unitgen.UnitOscillator; - -/** - * Play a tone using a JSyn oscillator. Modulate the amplitude using a DAHDSR envelope. - * - * @author Phil Burk (C) 2010 Mobileer Inc - */ -public class HearDAHDSR extends JApplet { - private static final long serialVersionUID = -2704222221111608377L; - private Synthesizer synth; - private UnitOscillator osc; - // Use a square wave to trigger the envelope. - private UnitOscillator gatingOsc; - private EnvelopeDAHDSR dahdsr; - private LineOut lineOut; - - @Override - public void init() { - synth = JSyn.createSynthesizer(); - - // Add a tone generator. - synth.add(osc = new SineOscillator()); - // Add a trigger. - synth.add(gatingOsc = new SquareOscillator()); - // Use an envelope to control the amplitude. - synth.add(dahdsr = new EnvelopeDAHDSR()); - // Add an output mixer. - synth.add(lineOut = new LineOut()); - - gatingOsc.output.connect(dahdsr.input); - dahdsr.output.connect(osc.amplitude); - dahdsr.attack.setup(0.001, 0.01, 2.0); - osc.output.connect(0, lineOut.input, 0); - osc.output.connect(0, lineOut.input, 1); - - gatingOsc.frequency.setup(0.001, 0.5, 10.0); - gatingOsc.frequency.setName("Rate"); - - osc.frequency.setup(50.0, 440.0, 2000.0); - osc.frequency.setName("Freq"); - - // Arrange the knob in a row. - setLayout(new GridLayout(1, 0)); - - setupPortKnob(osc.frequency); - setupPortKnob(gatingOsc.frequency); - setupPortKnob(dahdsr.attack); - setupPortKnob(dahdsr.hold); - setupPortKnob(dahdsr.decay); - setupPortKnob(dahdsr.sustain); - setupPortKnob(dahdsr.release); - - validate(); - } - - private void setupPortKnob(UnitInputPort port) { - - DoubleBoundedRangeModel model = PortModelFactory.createExponentialModel(port); - System.out.println("Make knob for " + port.getName() + ", model.getDV = " - + model.getDoubleValue() + ", model.getV = " + model.getValue() + ", port.getV = " - + port.get()); - RotaryTextController knob = new RotaryTextController(model, 10); - knob.setBorder(BorderFactory.createTitledBorder(port.getName())); - knob.setTitle(port.getName()); - add(knob); - } - - @Override - public void start() { - // Start synthesizer using default stereo output at 44100 Hz. - synth.start(); - // We only need to start the LineOut. It will pull data from the - // oscillator. - lineOut.start(); - } - - @Override - public void stop() { - synth.stop(); - } - - /* Can be run as either an application or as an applet. */ - public static void main(String args[]) { - HearDAHDSR applet = new HearDAHDSR(); - JAppletFrame frame = new JAppletFrame("Hear DAHDSR Envelope", applet); - frame.setSize(640, 200); - frame.setVisible(true); - frame.test(); - } - -} diff --git a/tests/com/jsyn/examples/HearMoogFilter.java b/tests/com/jsyn/examples/HearMoogFilter.java deleted file mode 100644 index 4ec4811..0000000 --- a/tests/com/jsyn/examples/HearMoogFilter.java +++ /dev/null @@ -1,208 +0,0 @@ -/* - * Copyright 2010 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.examples; - -import java.awt.BorderLayout; -import java.awt.GridLayout; -import java.awt.event.ItemEvent; -import java.awt.event.ItemListener; - -import javax.swing.BorderFactory; -import javax.swing.BoxLayout; -import javax.swing.ButtonGroup; -import javax.swing.JApplet; -import javax.swing.JLabel; -import javax.swing.JPanel; -import javax.swing.JRadioButton; - -import com.jsyn.JSyn; -import com.jsyn.Synthesizer; -import com.jsyn.ports.UnitInputPort; -import com.jsyn.scope.AudioScope; -import com.jsyn.scope.AudioScopeProbe; -import com.jsyn.swing.DoubleBoundedRangeModel; -import com.jsyn.swing.JAppletFrame; -import com.jsyn.swing.PortModelFactory; -import com.jsyn.swing.RotaryTextController; -import com.jsyn.unitgen.FilterFourPoles; -import com.jsyn.unitgen.FilterLowPass; -import com.jsyn.unitgen.LineOut; -import com.jsyn.unitgen.LinearRamp; -import com.jsyn.unitgen.PassThrough; -import com.jsyn.unitgen.SawtoothOscillatorBL; -import com.jsyn.unitgen.UnitOscillator; - -/** - * Play a sawtooth through a 4-pole filter. - * - * @author Phil Burk (C) 2010 Mobileer Inc - */ -public class HearMoogFilter extends JApplet { - private Synthesizer synth; - private UnitOscillator oscillator; - private FilterFourPoles filterMoog; - private FilterLowPass filterBiquad; - private LinearRamp rampCutoff; - private PassThrough tieQ; - private PassThrough tieCutoff; - private PassThrough mixer; - private LineOut lineOut; - - private AudioScope scope; - private boolean useCutoffRamp = false; - - @Override - public void init() { - synth = JSyn.createSynthesizer(); - synth.add(oscillator = new SawtoothOscillatorBL()); - synth.add(rampCutoff = new LinearRamp()); - synth.add(tieQ = new PassThrough()); - synth.add(tieCutoff = new PassThrough()); - synth.add(filterMoog = new FilterFourPoles()); - synth.add(filterBiquad = new FilterLowPass()); - synth.add(mixer = new PassThrough()); - synth.add(lineOut = new LineOut()); - - oscillator.output.connect(filterMoog.input); - oscillator.output.connect(filterBiquad.input); - if (useCutoffRamp) { - rampCutoff.output.connect(filterMoog.frequency); - rampCutoff.output.connect(filterBiquad.frequency); - rampCutoff.time.set(0.000); - } else { - tieCutoff.output.connect(filterMoog.frequency); - tieCutoff.output.connect(filterBiquad.frequency); - } - tieQ.output.connect(filterMoog.Q); - tieQ.output.connect(filterBiquad.Q); - filterMoog.output.connect(mixer.input); - mixer.output.connect(0, lineOut.input, 0); - mixer.output.connect(0, lineOut.input, 1); - - filterBiquad.amplitude.set(0.1); - oscillator.frequency.setup(50.0, 130.0, 3000.0); - oscillator.amplitude.setup(0.0, 0.336, 1.0); - rampCutoff.input.setup(filterMoog.frequency); - tieCutoff.input.setup(filterMoog.frequency); - tieQ.input.setup(0.1, 0.7, 10.0); - setupGUI(); - } - - private void setupGUI() { - setLayout(new BorderLayout()); - - add(new JLabel("Sawtooth through a \"Moog\" style filter."), BorderLayout.NORTH); - - JPanel rackPanel = new JPanel(); - rackPanel.setLayout(new BoxLayout(rackPanel, BoxLayout.Y_AXIS)); - - JPanel buttonPanel = new JPanel(); - ButtonGroup cbg = new ButtonGroup(); - JRadioButton radioButton = new JRadioButton("Moog", true); - cbg.add(radioButton); - radioButton.addItemListener(new ItemListener() { - @Override - public void itemStateChanged(ItemEvent e) { - mixer.input.disconnectAll(); - filterMoog.output.connect(mixer.input); - } - }); - buttonPanel.add(radioButton); - radioButton = new JRadioButton("Biquad", false); - cbg.add(radioButton); - radioButton.addItemListener(new ItemListener() { - @Override - public void itemStateChanged(ItemEvent e) { - mixer.input.disconnectAll(); - filterBiquad.output.connect(mixer.input); - } - }); - buttonPanel.add(radioButton); - - /* - * buttonPanel.add( new JLabel("Show:") ); cbg = new ButtonGroup(); radioButton = new - * JRadioButton( "Waveform", true ); cbg.add( radioButton ); radioButton.addItemListener( - * new ItemListener() { public void itemStateChanged( ItemEvent e ) { scope.setViewMode( - * AudioScope.ViewMode.WAVEFORM ); } } ); buttonPanel.add( radioButton ); radioButton = new - * JRadioButton( "Spectrum", true ); cbg.add( radioButton ); radioButton.addItemListener( - * new ItemListener() { public void itemStateChanged( ItemEvent e ) { scope.setViewMode( - * AudioScope.ViewMode.SPECTRUM ); } } ); buttonPanel.add( radioButton ); - */ - - rackPanel.add(buttonPanel); - - // Arrange the knobs in a row. - JPanel knobPanel = new JPanel(); - knobPanel.setLayout(new GridLayout(1, 0)); - - knobPanel.add(setupPortKnob(oscillator.frequency, "OscFreq")); - knobPanel.add(setupPortKnob(oscillator.amplitude, "OscAmp")); - - if (useCutoffRamp) { - knobPanel.add(setupPortKnob(rampCutoff.input, "Cutoff")); - } else { - knobPanel.add(setupPortKnob(tieCutoff.input, "Cutoff")); - } - knobPanel.add(setupPortKnob(tieQ.input, "Q")); - rackPanel.add(knobPanel); - add(rackPanel, BorderLayout.SOUTH); - - scope = new AudioScope(synth); - scope.addProbe(oscillator.output); - scope.addProbe(filterMoog.output); - scope.addProbe(filterBiquad.output); - scope.setTriggerMode(AudioScope.TriggerMode.NORMAL); - scope.getView().setControlsVisible(false); - add(scope.getView(), BorderLayout.CENTER); - scope.start(); - validate(); - } - - private RotaryTextController setupPortKnob(UnitInputPort port, String label) { - DoubleBoundedRangeModel model = PortModelFactory.createExponentialModel(port); - RotaryTextController knob = new RotaryTextController(model, 10); - knob.setBorder(BorderFactory.createTitledBorder(label)); - knob.setTitle(label); - return knob; - } - - @Override - public void start() { - // Start synthesizer using default stereo output at 44100 Hz. - synth.start(); - scope.start(); - // We only need to start the LineOut. It will pull data from the - // oscillator. - lineOut.start(); - } - - @Override - public void stop() { - scope.stop(); - synth.stop(); - } - - /* Can be run as either an application or as an applet. */ - public static void main(String args[]) { - HearMoogFilter applet = new HearMoogFilter(); - JAppletFrame frame = new JAppletFrame("Hear Moog Style Filter", applet); - frame.setSize(800, 600); - frame.setVisible(true); - frame.test(); - } - -} diff --git a/tests/com/jsyn/examples/HearSinePM.java b/tests/com/jsyn/examples/HearSinePM.java deleted file mode 100644 index 1e19505..0000000 --- a/tests/com/jsyn/examples/HearSinePM.java +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright 2010 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.examples; - -import java.awt.BorderLayout; -import java.awt.GridLayout; - -import javax.swing.BorderFactory; -import javax.swing.JApplet; -import javax.swing.JLabel; -import javax.swing.JPanel; - -import com.jsyn.JSyn; -import com.jsyn.Synthesizer; -import com.jsyn.ports.UnitInputPort; -import com.jsyn.scope.AudioScope; -import com.jsyn.swing.DoubleBoundedRangeModel; -import com.jsyn.swing.JAppletFrame; -import com.jsyn.swing.PortModelFactory; -import com.jsyn.swing.RotaryTextController; -import com.jsyn.unitgen.LineOut; -import com.jsyn.unitgen.SineOscillator; -import com.jsyn.unitgen.SineOscillatorPhaseModulated; - -/** - * Play a tone using a phase modulated sinewave oscillator. Phase modulation (PM) is very similar to - * frequency modulation (FM) but is easier to control. - * - * @author Phil Burk (C) 2010 Mobileer Inc - */ -public class HearSinePM extends JApplet { - private static final long serialVersionUID = -2704222221111608377L; - private Synthesizer synth; - SineOscillatorPhaseModulated carrier; - SineOscillator modulator; - LineOut lineOut; - AudioScope scope; - - @Override - public void init() { - synth = JSyn.createSynthesizer(); - // Add a tone generator. - synth.add(modulator = new SineOscillator()); - // Add a trigger. - synth.add(carrier = new SineOscillatorPhaseModulated()); - // Add an output mixer. - synth.add(lineOut = new LineOut()); - - modulator.output.connect(carrier.modulation); - carrier.output.connect(0, lineOut.input, 0); - carrier.output.connect(0, lineOut.input, 1); - modulator.amplitude.setup(0.0, 1.0, 10.0); - carrier.amplitude.setup(0.0, 0.25, 1.0); - setupGUI(); - } - - private void setupGUI() { - setLayout(new BorderLayout()); - - add(new JLabel("Show Phase Modulation in an AudioScope"), BorderLayout.NORTH); - - // Arrange the knob in a row. - JPanel knobPanel = new JPanel(); - knobPanel.setLayout(new GridLayout(1, 0)); - - knobPanel.add(setupPortKnob(modulator.frequency, "MFreq")); - knobPanel.add(setupPortKnob(modulator.amplitude, "MAmp")); - knobPanel.add(setupPortKnob(carrier.frequency, "CFreq")); - knobPanel.add(setupPortKnob(carrier.amplitude, "CAmp")); - add(knobPanel, BorderLayout.SOUTH); - - scope = new AudioScope(synth); - scope.addProbe(carrier.output); - scope.addProbe(modulator.output); - scope.setTriggerMode(AudioScope.TriggerMode.NORMAL); - scope.getView().setControlsVisible(true); - add(scope.getView(), BorderLayout.CENTER); - scope.start(); - validate(); - } - - private RotaryTextController setupPortKnob(UnitInputPort port, String label) { - DoubleBoundedRangeModel model = PortModelFactory.createExponentialModel(port); - RotaryTextController knob = new RotaryTextController(model, 10); - knob.setBorder(BorderFactory.createTitledBorder(label)); - knob.setTitle(label); - return knob; - } - - @Override - public void start() { - // Start synthesizer using default stereo output at 44100 Hz. - synth.start(); - scope.start(); - // We only need to start the LineOut. It will pull data from the - // oscillator. - lineOut.start(); - } - - @Override - public void stop() { - scope.stop(); - synth.stop(); - } - - /* Can be run as either an application or as an applet. */ - public static void main(String args[]) { - HearSinePM applet = new HearSinePM(); - JAppletFrame frame = new JAppletFrame("Hear Phase Modulation", applet); - frame.setSize(640, 400); - frame.setVisible(true); - frame.test(); - } - -} diff --git a/tests/com/jsyn/examples/HearSpectralFilter.java b/tests/com/jsyn/examples/HearSpectralFilter.java deleted file mode 100644 index 93559b4..0000000 --- a/tests/com/jsyn/examples/HearSpectralFilter.java +++ /dev/null @@ -1,206 +0,0 @@ -/* - * Copyright 2010 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.examples; - -import java.io.File; -import java.io.IOException; - -import com.jsyn.JSyn; -import com.jsyn.Synthesizer; -import com.jsyn.data.Spectrum; -import com.jsyn.unitgen.LineOut; -import com.jsyn.unitgen.PassThrough; -import com.jsyn.unitgen.SawtoothOscillatorBL; -import com.jsyn.unitgen.SineOscillator; -import com.jsyn.unitgen.SpectralFilter; -import com.jsyn.unitgen.SpectralProcessor; -import com.jsyn.unitgen.UnitOscillator; -import com.jsyn.unitgen.WhiteNoise; -import com.jsyn.util.WaveRecorder; - -/** - * Play a sine sweep through an FFT/IFFT pair. - * - * @author Phil Burk (C) 2010 Mobileer Inc - */ -public class HearSpectralFilter { - private Synthesizer synth; - private PassThrough center; - private UnitOscillator osc; - private UnitOscillator lfo; - private PassThrough mixer; - private SpectralFilter filter; - private LineOut lineOut; - private WaveRecorder recorder; - private final static boolean useRecorder = true; - private final static boolean useProcessor = true; - private final static int NUM_FFTS = 4; - private final static int SIZE_LOG_2 = 10; - private final static int SIZE = 1 << SIZE_LOG_2; - private SpectralProcessor[] processors; - private WhiteNoise noise; - private static int SAMPLE_RATE = 44100; - - private static class CustomSpectralProcessor extends SpectralProcessor { - public CustomSpectralProcessor() { - super(SIZE); - } - - @Override - public void processSpectrum(Spectrum inputSpectrum, Spectrum outputSpectrum) { - // pitchUpOctave( inputSpectrum, outputSpectrum ); - lowPassFilter(inputSpectrum, outputSpectrum, 1500.0); - } - - public void lowPassFilter(Spectrum inputSpectrum, Spectrum outputSpectrum, double frequency) { - inputSpectrum.copyTo(outputSpectrum); - double[] outReal = outputSpectrum.getReal(); - double[] outImag = outputSpectrum.getImaginary(); - // brickwall filter - int size = outReal.length; - int cutoff = (int) (frequency * size / SAMPLE_RATE); - int nyquist = size / 2; - for (int i = cutoff; i < nyquist; i++) { - // Bins above nyquist are mirror of ones below. - outReal[i] = outReal[size - i] = 0.0; - outImag[i] = outImag[size - i] = 0.0; - } - } - - // TODO Figure out why this sounds bad. - public void pitchUpOctave(Spectrum inputSpectrum, Spectrum outputSpectrum) { - outputSpectrum.clear(); - double[] inReal = inputSpectrum.getReal(); - double[] inImag = inputSpectrum.getImaginary(); - double[] outReal = outputSpectrum.getReal(); - double[] outImag = outputSpectrum.getImaginary(); - int size = inReal.length; - int nyquist = size / 2; - // Octave doubling by shifting the spectrum. - for (int i = nyquist - 2; i > 1; i--) { - int h = i / 2; - outReal[i] = inReal[h]; - outImag[i] = inImag[h]; - outReal[size - i] = inReal[size - h]; - outImag[size - i] = inImag[size - h]; - } - } - } - - private void test() throws IOException { - // Create a context for the synthesizer. - synth = JSyn.createSynthesizer(); - synth.setRealTime(true); - - if (useRecorder) { - File waveFile = new File("temp_recording.wav"); - // Default is stereo, 16 bits. - recorder = new WaveRecorder(synth, waveFile); - System.out.println("Writing to WAV file " + waveFile.getAbsolutePath()); - } - - if (useProcessor) { - processors = new SpectralProcessor[NUM_FFTS]; - for (int i = 0; i < NUM_FFTS; i++) { - processors[i] = new CustomSpectralProcessor(); - } - } - - // Add a tone generator. - synth.add(center = new PassThrough()); - synth.add(lfo = new SineOscillator()); - synth.add(noise = new WhiteNoise()); - synth.add(mixer = new PassThrough()); - - synth.add(osc = new SawtoothOscillatorBL()); - // synth.add( osc = new SineOscillator() ); - - synth.add(filter = new SpectralFilter(NUM_FFTS, SIZE_LOG_2)); - // Add a stereo audio output unit. - synth.add(lineOut = new LineOut()); - - center.output.connect(osc.frequency); - lfo.output.connect(osc.frequency); - osc.output.connect(mixer.input); - noise.output.connect(mixer.input); - mixer.output.connect(filter.input); - if (useProcessor) { - // Pass spectra through a custom processor. - for (int i = 0; i < NUM_FFTS; i++) { - filter.getSpectralOutput(i).connect(processors[i].input); - processors[i].output.connect(filter.getSpectralInput(i)); - } - } else { - for (int i = 0; i < NUM_FFTS; i++) { - // Connect FFTs directly to IFFTs for passthrough. - filter.getSpectralOutput(i).connect(filter.getSpectralInput(i)); - } - - } - mixer.output.connect(0, lineOut.input, 0); - filter.output.connect(0, lineOut.input, 1); - - // Set the frequency and amplitude for the modulated sine wave. - center.input.set(600.0); - lfo.frequency.set(0.2); - lfo.amplitude.set(400.0); - osc.amplitude.set(0.2); - noise.amplitude.set(0.2); - - synth.start(SAMPLE_RATE); - - if (useRecorder) { - mixer.output.connect(0, recorder.getInput(), 0); - filter.output.connect(0, recorder.getInput(), 1); - // When we start the recorder it will pull data from the oscillator - // and sweeper. - recorder.start(); - } - - lineOut.start(); - - System.out.println("You should now be hearing a clean oscillator on the left channel,"); - System.out.println("and the FFT->IFFT processed signal on the right channel."); - - // Sleep while the sound is generated in the background. - try { - double time = synth.getCurrentTime(); - // Sleep for a few seconds. - synth.sleepUntil(time + 10.0); - } catch (InterruptedException e) { - e.printStackTrace(); - } - - if (recorder != null) { - recorder.stop(); - recorder.close(); - } - - System.out.println("Stop playing. -------------------"); - // Stop everything. - synth.stop(); - } - - public static void main(String[] args) { - try { - new HearSpectralFilter().test(); - } catch (IOException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - } -} diff --git a/tests/com/jsyn/examples/ListAudioDevices.java b/tests/com/jsyn/examples/ListAudioDevices.java deleted file mode 100644 index dceaa0d..0000000 --- a/tests/com/jsyn/examples/ListAudioDevices.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * 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.examples; - -import com.jsyn.devices.AudioDeviceFactory; -import com.jsyn.devices.AudioDeviceManager; - -public class ListAudioDevices { - - /** - * @param args - */ - public static void main(String[] args) { - AudioDeviceManager audioManager = AudioDeviceFactory.createAudioDeviceManager(); - - int numDevices = audioManager.getDeviceCount(); - for (int i = 0; i < numDevices; i++) { - String deviceName = audioManager.getDeviceName(i); - int maxInputs = audioManager.getMaxInputChannels(i); - int maxOutputs = audioManager.getMaxInputChannels(i); - boolean isDefaultInput = (i == audioManager.getDefaultInputDeviceID()); - boolean isDefaultOutput = (i == audioManager.getDefaultOutputDeviceID()); - System.out.println("#" + i + " : " + deviceName); - System.out.println(" max inputs : " + maxInputs - + (isDefaultInput ? " (default)" : "")); - System.out.println(" max outputs: " + maxOutputs - + (isDefaultOutput ? " (default)" : "")); - } - - } - -} diff --git a/tests/com/jsyn/examples/LongEcho.java b/tests/com/jsyn/examples/LongEcho.java deleted file mode 100644 index 0a86efd..0000000 --- a/tests/com/jsyn/examples/LongEcho.java +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright 2010 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.examples; - -import java.io.File; -import java.io.IOException; - -import com.jsyn.JSyn; -import com.jsyn.Synthesizer; -import com.jsyn.data.FloatSample; -import com.jsyn.devices.AudioDeviceManager; -import com.jsyn.unitgen.ChannelIn; -import com.jsyn.unitgen.ChannelOut; -import com.jsyn.unitgen.FixedRateMonoReader; -import com.jsyn.unitgen.FixedRateMonoWriter; -import com.jsyn.unitgen.Maximum; -import com.jsyn.unitgen.Minimum; -import com.jsyn.util.WaveFileWriter; - -/** - * Echo the input using a circular buffer in a sample. - * - * @author Phil Burk (C) 2010 Mobileer Inc - */ -public class LongEcho { - final static int DELAY_SECONDS = 4; - Synthesizer synth; - ChannelIn channelIn; - ChannelOut channelOut; - FloatSample sample; - FixedRateMonoReader reader; - FixedRateMonoWriter writer; - Minimum minner; - Maximum maxxer; - - private void test() throws IOException { - // Create a context for the synthesizer. - synth = JSyn.createSynthesizer(); - synth.add(channelIn = new ChannelIn()); - synth.add(channelOut = new ChannelOut()); - - synth.add(minner = new Minimum()); - synth.add(maxxer = new Maximum()); - synth.add(reader = new FixedRateMonoReader()); - synth.add(writer = new FixedRateMonoWriter()); - - sample = new FloatSample(44100 * DELAY_SECONDS, 1); - - maxxer.inputB.set(-0.98); // clip - minner.inputB.set(0.98); - - // Connect the input to the output. - channelIn.output.connect(minner.inputA); - minner.output.connect(maxxer.inputA); - maxxer.output.connect(writer.input); - - reader.output.connect(channelOut.input); - - // Both stereo. - int numInputChannels = 2; - int numOutputChannels = 2; - synth.start(44100, AudioDeviceManager.USE_DEFAULT_DEVICE, numInputChannels, - AudioDeviceManager.USE_DEFAULT_DEVICE, numOutputChannels); - - writer.start(); - channelOut.start(); - - // For a long echo, read cursor should be just in front of the write cursor. - reader.dataQueue.queue(sample, 1000, sample.getNumFrames() - 1000); - // Loop both forever. - reader.dataQueue.queueLoop(sample, 0, sample.getNumFrames()); - writer.dataQueue.queueLoop(sample, 0, sample.getNumFrames()); - System.out.println("Start talking. You should hear an echo after " + DELAY_SECONDS - + " seconds."); - // Sleep a while. - try { - double time = synth.getCurrentTime(); - // Sleep for a while - synth.sleepUntil(time + 30.0); - } catch (InterruptedException e) { - e.printStackTrace(); - } - - saveEcho(new File("saved_echo.wav")); - // Stop everything. - synth.stop(); - } - - private void saveEcho(File file) throws IOException { - WaveFileWriter writer = new WaveFileWriter(file); - writer.setFrameRate(44100); - writer.setSamplesPerFrame(1); - writer.setBitsPerSample(16); - float[] buffer = new float[sample.getNumFrames()]; - sample.read(buffer); - for (float v : buffer) { - writer.write(v); - } - writer.close(); - } - - public static void main(String[] args) { - try { - new LongEcho().test(); - } catch (IOException e) { - e.printStackTrace(); - } - } -} diff --git a/tests/com/jsyn/examples/MonoPassThrough.java b/tests/com/jsyn/examples/MonoPassThrough.java deleted file mode 100644 index 0e81abf..0000000 --- a/tests/com/jsyn/examples/MonoPassThrough.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright 2010 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.examples; - -import com.jsyn.JSyn; -import com.jsyn.Synthesizer; -import com.jsyn.devices.AudioDeviceManager; -import com.jsyn.unitgen.ChannelIn; -import com.jsyn.unitgen.ChannelOut; - -/** - * Pass audio input to audio output. - * - * @author Phil Burk (C) 2010 Mobileer Inc - */ -public class MonoPassThrough { - Synthesizer synth; - ChannelIn channelIn; - ChannelOut channelOut; - - private void test() { - // Create a context for the synthesizer. - synth = JSyn.createSynthesizer(); - synth.add(channelIn = new ChannelIn()); - synth.add(channelOut = new ChannelOut()); - // Connect the input to the output. - channelIn.output.connect(channelOut.input); - - // Both stereo. - int numInputChannels = 2; - int numOutputChannels = 2; - synth.start(48000, AudioDeviceManager.USE_DEFAULT_DEVICE, numInputChannels, - AudioDeviceManager.USE_DEFAULT_DEVICE, numOutputChannels); - - // We only need to start the ChannelOut. It will pull data from the ChannelIn. - channelOut.start(); - // Sleep a while. - try { - double time = synth.getCurrentTime(); - // Sleep for a few seconds. - synth.sleepUntil(time + 4.0); - } catch (InterruptedException e) { - e.printStackTrace(); - } - // Stop everything. - synth.stop(); - } - - public static void main(String[] args) { - new MonoPassThrough().test(); - } -} diff --git a/tests/com/jsyn/examples/NotesToTone.java b/tests/com/jsyn/examples/NotesToTone.java deleted file mode 100644 index 9186087..0000000 --- a/tests/com/jsyn/examples/NotesToTone.java +++ /dev/null @@ -1,214 +0,0 @@ -/* - * Copyright 2010 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. - */ -/** - * If you play notes fast enough they become a tone. - * - * Play a sine wave modulated by an envelope. - * Speed up the envelope until it is playing at audio rate. - * Slow down the oscillator until it becomes an LFO amp modulator. - * Use a LatchZeroCrossing to stop at the end of a sine wave cycle when we are finished. - * - * @author Phil Burk, (C) 2010 Mobileer Inc - */ - -package com.jsyn.examples; - -import java.io.File; -import java.io.IOException; - -import com.jsyn.JSyn; -import com.jsyn.Synthesizer; -import com.jsyn.data.SegmentedEnvelope; -import com.jsyn.unitgen.ExponentialRamp; -import com.jsyn.unitgen.LatchZeroCrossing; -import com.jsyn.unitgen.LineOut; -import com.jsyn.unitgen.SineOscillator; -import com.jsyn.unitgen.UnitOscillator; -import com.jsyn.unitgen.VariableRateDataReader; -import com.jsyn.unitgen.VariableRateMonoReader; -import com.jsyn.util.WaveRecorder; - -/** - * When notes speed up they can become a new tone.
- * Multiply an oscillator and an envelope. Speed up the envelope until it becomes a tone. Slow down - * the oscillator until it acts like an envelope. Write the resulting audio to a WAV file. - * - * @author Phil Burk (C) 2011 Mobileer Inc - */ - -public class NotesToTone { - private final static double SONG_AMPLITUDE = 0.7; - private final static double INTRO_DURATION = 2.0; - private final static double OUTRO_DURATION = 2.0; - private final static double RAMP_DURATION = 20.0; - private final static double LOW_FREQUENCY = 1.0; - private final static double HIGH_FREQUENCY = 800.0; - - private final static boolean useRecorder = true; - private WaveRecorder recorder; - - private Synthesizer synth; - private ExponentialRamp envSweeper; - private ExponentialRamp oscSweeper; - private VariableRateDataReader envelopePlayer; - private UnitOscillator osc; - private LatchZeroCrossing latch; - private LineOut lineOut; - private SegmentedEnvelope envelope; - - private void play() throws IOException { - synth = JSyn.createSynthesizer(); - synth.setRealTime(true); - - if (useRecorder) { - File waveFile = new File("notes_to_tone.wav"); - // Default is stereo, 16 bits. - recorder = new WaveRecorder(synth, waveFile, 1); - System.out.println("Writing to WAV file " + waveFile.getAbsolutePath()); - } - - createUnits(); - - connectUnits(); - - setupEnvelope(); - - osc.amplitude.set(SONG_AMPLITUDE); - - // Ramp the rate of the envelope up until it becomes an audible tone. - envSweeper.current.set(LOW_FREQUENCY); - envSweeper.input.set(LOW_FREQUENCY); - envSweeper.time.set(RAMP_DURATION); - - // Ramp the rate of the oscillator down until it becomes an LFO. - oscSweeper.current.set(HIGH_FREQUENCY); - oscSweeper.input.set(HIGH_FREQUENCY); - oscSweeper.time.set(RAMP_DURATION); - - // Start synthesizer using default stereo output at 44100 Hz. - synth.start(); - - // When we start the recorder it will pull data from the oscillator and - // sweeper. - if (recorder != null) { - recorder.start(); - } - - // We also need to start the LineOut if we want to hear it now. - lineOut.start(); - - // Get synthesizer time in seconds. - double timeNow = synth.getCurrentTime(); - - // Schedule start of ramps. - double songDuration = INTRO_DURATION + RAMP_DURATION + OUTRO_DURATION; - envSweeper.input.set(HIGH_FREQUENCY, timeNow + INTRO_DURATION); - oscSweeper.input.set(LOW_FREQUENCY, timeNow + INTRO_DURATION); - - // Arm zero crossing latch - latch.gate.set(0.0, timeNow + songDuration); - - // Sleep while the sound is being generated in the background thread. - try { - synth.sleepUntil(timeNow + songDuration + 2.0); - } catch (InterruptedException e) { - e.printStackTrace(); - } - - if (recorder != null) { - recorder.stop(); - recorder.close(); - } - // Stop everything. - synth.stop(); - } - - private void createUnits() { - // Add a tone generators. - synth.add(osc = new SineOscillator()); - // Add a controller that will sweep the envelope rate up. - synth.add(envSweeper = new ExponentialRamp()); - // Add a controller that will sweep the oscillator down. - synth.add(oscSweeper = new ExponentialRamp()); - - synth.add(latch = new LatchZeroCrossing()); - // Add an output unit. - synth.add(lineOut = new LineOut()); - - // Add an envelope player. - synth.add(envelopePlayer = new VariableRateMonoReader()); - } - - private void connectUnits() { - oscSweeper.output.connect(osc.frequency); - osc.output.connect(latch.input); - // Latch when sine LFO crosses zero. - latch.output.connect(envelopePlayer.amplitude); - - envSweeper.output.connect(envelopePlayer.rate); - - // Connect the envelope player to the audio output. - envelopePlayer.output.connect(0, lineOut.input, 0); - // crossFade.output.connect( 0, lineOut.input, 1 ); - - if (recorder != null) { - envelopePlayer.output.connect(0, recorder.getInput(), 0); - // crossFade.output.connect( 0, recorder.getInput(), 1 ); - } - } - - private void setupEnvelope() { - // Setup envelope. The envelope has a total duration of 1.0 seconds. - // Values are (duration,target) pairs. - double[] pairs = new double[5 * 2 * 2]; - int i = 0; - // duration, target for delay - pairs[i++] = 0.15; - pairs[i++] = 0.0; - // duration, target for attack - pairs[i++] = 0.05; - pairs[i++] = 1.0; - // duration, target for release - pairs[i++] = 0.1; - pairs[i++] = 0.6; - // duration, target for sustain - pairs[i++] = 0.1; - pairs[i++] = 0.6; - // duration, target for release - pairs[i++] = 0.1; - pairs[i++] = 0.0; - // Create mirror image of this envelope. - int halfLength = i; - while (i < pairs.length) { - pairs[i] = pairs[i - halfLength]; - i++; - pairs[i] = pairs[i - halfLength] * -1.0; - i++; - } - envelope = new SegmentedEnvelope(pairs); - - envelopePlayer.dataQueue.queueLoop(envelope, 0, envelope.getNumFrames()); - } - - public static void main(String[] args) { - try { - new NotesToTone().play(); - } catch (IOException e) { - e.printStackTrace(); - } - } - -} diff --git a/tests/com/jsyn/examples/PlayChords.java b/tests/com/jsyn/examples/PlayChords.java deleted file mode 100644 index 28cab5f..0000000 --- a/tests/com/jsyn/examples/PlayChords.java +++ /dev/null @@ -1,180 +0,0 @@ -/* - * 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.examples; - -import com.jsyn.JSyn; -import com.jsyn.Synthesizer; -import com.jsyn.instruments.SubtractiveSynthVoice; -import com.jsyn.unitgen.LineOut; -import com.jsyn.unitgen.UnitVoice; -import com.jsyn.util.VoiceAllocator; -import com.softsynth.math.AudioMath; -import com.softsynth.shared.time.TimeStamp; - -/** - * Play chords and melody using the VoiceAllocator. - * - * @author Phil Burk (C) 2009 Mobileer Inc - */ -public class PlayChords { - private static final int MAX_VOICES = 8; - private Synthesizer synth; - private VoiceAllocator allocator; - private LineOut lineOut; - /** Number of seconds to generate music in advance of presentation-time. */ - private double advance = 0.2; - private double secondsPerBeat = 0.6; - // on time over note duration - private double dutyCycle = 0.8; - private double measure = secondsPerBeat * 4.0; - private UnitVoice[] voices; - - private void test() { - synth = JSyn.createSynthesizer(); - - // Add an output. - synth.add(lineOut = new LineOut()); - - voices = new UnitVoice[MAX_VOICES]; - for (int i = 0; i < MAX_VOICES; i++) { - SubtractiveSynthVoice voice = new SubtractiveSynthVoice(); - synth.add(voice); - voice.getOutput().connect(0, lineOut.input, 0); - voice.getOutput().connect(0, lineOut.input, 1); - voices[i] = voice; - } - allocator = new VoiceAllocator(voices); - - // Start synthesizer using default stereo output at 44100 Hz. - synth.start(); - // We only need to start the LineOut. It will pull data from the - // voices. - lineOut.start(); - - // Get synthesizer time in seconds. - double timeNow = synth.getCurrentTime(); - - // Advance to a near future time so we have a clean start. - double time = timeNow + 1.0; - - try { - int tonic = 60 - 12; - for (int i = 0; i < 4; i++) { - playMajorMeasure1(time, tonic); - time += measure; - catchUp(time); - playMajorMeasure1(time, tonic + 4); - time += measure; - catchUp(time); - playMajorMeasure1(time, tonic + 7); - time += measure; - catchUp(time); - playMinorMeasure1(time, tonic + 2); // minor chord - time += measure; - catchUp(time); - } - time += secondsPerBeat; - catchUp(time); - - } catch (InterruptedException e) { - e.printStackTrace(); - } - - // Stop everything. - synth.stop(); - } - - private void playMinorMeasure1(double time, int base) throws InterruptedException { - int p1 = base; - int p2 = base + 3; - int p3 = base + 7; - playChord1(time, p1, p2, p3); - playNoodle1(time, p1 + 24, p2 + 24, p3 + 24); - } - - private void playMajorMeasure1(double time, int base) throws InterruptedException { - int p1 = base; - int p2 = base + 4; - int p3 = base + 7; - playChord1(time, p1, p2, p3); - playNoodle1(time, p1 + 24, p2 + 24, p3 + 24); - } - - private void playNoodle1(double time, int p1, int p2, int p3) { - double secondsPerNote = secondsPerBeat * 0.5; - for (int i = 0; i < 8; i++) { - int p = pickFromThree(p1, p2, p3); - noteOn(time, p); - noteOff(time + dutyCycle * secondsPerNote, p); - time += secondsPerNote; - } - } - - private int pickFromThree(int p1, int p2, int p3) { - int r = (int) (Math.random() * 3.0); - if (r < 1) - return p1; - else if (r < 2) - return p2; - else - return p3; - } - - private void playChord1(double time, int p1, int p2, int p3) throws InterruptedException { - double dur = dutyCycle * secondsPerBeat; - playTriad(time, dur, p1, p2, p3); - time += secondsPerBeat; - playTriad(time, dur, p1, p2, p3); - time += secondsPerBeat; - playTriad(time, dur * 0.25, p1, p2, p3); - time += secondsPerBeat * 0.25; - playTriad(time, dur * 0.25, p1, p2, p3); - time += secondsPerBeat * 0.75; - playTriad(time, dur, p1, p2, p3); - time += secondsPerBeat; - } - - private void playTriad(double time, double dur, int p1, int p2, int p3) - throws InterruptedException { - noteOn(time, p1); - noteOn(time, p2); - noteOn(time, p3); - double offTime = time + dur; - noteOff(offTime, p1); - noteOff(offTime, p2); - noteOff(offTime, p3); - } - - private void catchUp(double time) throws InterruptedException { - synth.sleepUntil(time - advance); - } - - private void noteOff(double time, int noteNumber) { - allocator.noteOff(noteNumber, new TimeStamp(time)); - } - - private void noteOn(double time, int noteNumber) { - double frequency = AudioMath.pitchToFrequency(noteNumber); - double amplitude = 0.2; - TimeStamp timeStamp = new TimeStamp(time); - allocator.noteOn(noteNumber, frequency, amplitude, timeStamp); - } - - public static void main(String[] args) { - new PlayChords().test(); - } -} diff --git a/tests/com/jsyn/examples/PlayCustomUnit.java b/tests/com/jsyn/examples/PlayCustomUnit.java deleted file mode 100644 index 609c351..0000000 --- a/tests/com/jsyn/examples/PlayCustomUnit.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright 2010 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.examples; - -import com.jsyn.JSyn; -import com.jsyn.Synthesizer; -import com.jsyn.unitgen.LineOut; -import com.jsyn.unitgen.SineOscillator; -import com.jsyn.unitgen.UnitOscillator; - -/** - * Play a tone using a JSyn oscillator and process it using a custom unit generator. - * - * @author Phil Burk (C) 2010 Mobileer Inc - */ -public class PlayCustomUnit { - private Synthesizer synth; - private UnitOscillator osc; - private CustomCubeUnit cuber; - private LineOut lineOut; - - private void test() { - synth = JSyn.createSynthesizer(); - // Add a tone generator. - synth.add(osc = new SineOscillator()); - // Add a tone generator. - synth.add(cuber = new CustomCubeUnit()); - // Add an output to the DAC. - synth.add(lineOut = new LineOut()); - // Connect the oscillator to the cuber. - osc.output.connect(0, cuber.input, 0); - // Connect the cuber to the right output. - cuber.output.connect(0, lineOut.input, 1); - // Send the original to the left output for comparison. - osc.output.connect(0, lineOut.input, 0); - - osc.frequency.set(240.0); - - // Start synthesizer using default stereo output at 44100 Hz. - synth.start(); - // We only need to start the LineOut. - // It will pull data from the cuber and the oscillator. - lineOut.start(); - // Sleep while the sound is generated in the background. - try { - double time = synth.getCurrentTime(); - // Sleep for a few seconds. - synth.sleepUntil(time + 10.0); - } catch (InterruptedException e) { - e.printStackTrace(); - } - // Stop everything. - synth.stop(); - } - - public static void main(String[] args) { - new PlayCustomUnit().test(); - } -} diff --git a/tests/com/jsyn/examples/PlayFunction.java b/tests/com/jsyn/examples/PlayFunction.java deleted file mode 100644 index 700152b..0000000 --- a/tests/com/jsyn/examples/PlayFunction.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright 2010 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.examples; - -import com.jsyn.JSyn; -import com.jsyn.Synthesizer; -import com.jsyn.data.Function; -import com.jsyn.unitgen.FunctionOscillator; -import com.jsyn.unitgen.LineOut; - -/** - * Play a tone using a FunctionOscillator. - * - * @author Phil Burk (C) 2010 Mobileer Inc - */ -public class PlayFunction { - Synthesizer synth; - FunctionOscillator osc; - LineOut lineOut; - - private void test() { - // Create a context for the synthesizer. - synth = JSyn.createSynthesizer(); - - // Start synthesizer using default stereo output at 44100 Hz. - synth.start(); - - // Add a FunctionOscillator - synth.add(osc = new FunctionOscillator()); - - // Define a function that gives the shape of the waveform. - Function func = new Function() { - @Override - public double evaluate(double input) { - // Input ranges from -1.0 to 1.0 - double s = Math.sin(input * Math.PI * 2.0); - double cubed = s * s * s; - return cubed; - } - }; - osc.function.set(func); - - // Add a stereo audio output unit. - synth.add(lineOut = new LineOut()); - - // Connect the oscillator to both channels of the output. - osc.output.connect(0, lineOut.input, 0); - osc.output.connect(0, lineOut.input, 1); - - // Set the frequency and amplitude for the sine wave. - osc.frequency.set(345.0); - osc.amplitude.set(0.6); - - // We only need to start the LineOut. It will pull data from the - // oscillator. - lineOut.start(); - - System.out.println("You should now be hearing a sine wave. ---------"); - - // Sleep while the sound is generated in the background. - try { - double time = synth.getCurrentTime(); - // Sleep for a few seconds. - synth.sleepUntil(time + 4.0); - } catch (InterruptedException e) { - e.printStackTrace(); - } - - System.out.println("Stop playing. -------------------"); - // Stop everything. - synth.stop(); - } - - public static void main(String[] args) { - new PlayFunction().test(); - } -} diff --git a/tests/com/jsyn/examples/PlayGrains.java b/tests/com/jsyn/examples/PlayGrains.java deleted file mode 100644 index b9a7b5b..0000000 --- a/tests/com/jsyn/examples/PlayGrains.java +++ /dev/null @@ -1,208 +0,0 @@ -/* - * Copyright 2011 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.examples; - -import java.awt.BorderLayout; -import java.awt.GridLayout; -import java.io.File; -import java.io.IOException; - -import javax.swing.BorderFactory; -import javax.swing.JApplet; -import javax.swing.JLabel; -import javax.swing.JPanel; - -import com.jsyn.JSyn; -import com.jsyn.Synthesizer; -import com.jsyn.data.FloatSample; -import com.jsyn.ports.UnitInputPort; -import com.jsyn.scope.AudioScope; -import com.jsyn.swing.DoubleBoundedRangeModel; -import com.jsyn.swing.JAppletFrame; -import com.jsyn.swing.PortModelFactory; -import com.jsyn.swing.RotaryTextController; -import com.jsyn.unitgen.ContinuousRamp; -import com.jsyn.unitgen.GrainFarm; -import com.jsyn.unitgen.LineOut; -import com.jsyn.unitgen.SampleGrainFarm; -import com.jsyn.util.SampleLoader; -import com.jsyn.util.WaveRecorder; - -/** - * Play with Granular Synthesis tools. - * - * @author Phil Burk (C) 2011 Mobileer Inc - */ -public class PlayGrains extends JApplet { - private static final long serialVersionUID = -8315903842197137926L; - private Synthesizer synth; - private LineOut lineOut; - private AudioScope scope; - private GrainFarm grainFarm; - private ContinuousRamp ramp; - private static final int NUM_GRAINS = 32; - private FloatSample sample; - private WaveRecorder recorder; - private final static boolean useRecorder = false; - - private static final boolean useSample = false; - // If you enable useSample then you will need to replace the file name below with a valid - // file name on your computer. - private File sampleFile = new File("/Users/phil/Music/samples/ChewyMonkeysWhistle.aiff"); - - /* Can be run as either an application or as an applet. */ - public static void main(String args[]) { - PlayGrains applet = new PlayGrains(); - JAppletFrame frame = new JAppletFrame("PlayGrains", applet); - frame.setSize(840, 500); - frame.setVisible(true); - frame.test(); - } - - private void setupGUI() { - setLayout(new BorderLayout()); - - add(BorderLayout.NORTH, - new JLabel("PlayGrains in an AudioScope - JSyn V" + synth.getVersion())); - - scope = new AudioScope(synth); - - // scope.addProbe( osc.output ); - scope.addProbe(grainFarm.output); - - scope.setTriggerMode(AudioScope.TriggerMode.NORMAL); - scope.getView().setControlsVisible(true); - add(BorderLayout.CENTER, scope.getView()); - scope.start(); - - // Arrange the knob in a row. - JPanel knobPanel = new JPanel(); - knobPanel.setLayout(new GridLayout(1, 0)); - - if (useSample) { - SampleGrainFarm sampleGrainFarm = (SampleGrainFarm) grainFarm; - knobPanel.add(setupLinearPortKnob(ramp.time, 0.001, 10.0, "Time")); - knobPanel.add(setupLinearPortKnob(ramp.input, -1.0, 1.0, "Position")); - knobPanel.add(setupLinearPortKnob(sampleGrainFarm.positionRange, 0.0, 0.5, "PosRange")); - } - knobPanel.add(setupPortKnob(grainFarm.density, 1.0, "Density")); - knobPanel.add(setupPortKnob(grainFarm.rate, 4.0, "Rate")); - knobPanel.add(setupPortKnob(grainFarm.rateRange, 3.0, "RateRange")); - knobPanel.add(setupPortKnob(grainFarm.duration, 0.1, "Duration")); - knobPanel.add(setupPortKnob(grainFarm.durationRange, 3.0, "DurRange")); - knobPanel.add(setupPortKnob(grainFarm.amplitude, 6.0, "Amplitude")); - knobPanel.add(setupPortKnob(grainFarm.amplitudeRange, 1.0, "AmpRange")); - add(knobPanel, BorderLayout.SOUTH); - - validate(); - } - - private RotaryTextController setupLinearPortKnob(UnitInputPort port, double min, double max, - String label) { - port.setMinimum(min); - port.setMaximum(max); - - DoubleBoundedRangeModel model = PortModelFactory.createLinearModel(port); - RotaryTextController knob = new RotaryTextController(model, 10); - knob.setBorder(BorderFactory.createTitledBorder(label)); - knob.setTitle(label); - return knob; - } - - private RotaryTextController setupPortKnob(UnitInputPort port, double max, String label) { - port.setMinimum(0.0); - port.setMaximum(max); - - DoubleBoundedRangeModel model = PortModelFactory.createExponentialModel(port); - RotaryTextController knob = new RotaryTextController(model, 10); - knob.setBorder(BorderFactory.createTitledBorder(label)); - knob.setTitle(label); - return knob; - } - - @Override - public void start() { - synth = JSyn.createSynthesizer(); - - try { - - if (useRecorder) { - File waveFile = new File("temp_recording.wav"); - // Record mono 16 bits. - recorder = new WaveRecorder(synth, waveFile, 1); - System.out.println("Writing to WAV file " + waveFile.getAbsolutePath()); - } - - if (useSample) { - sample = SampleLoader.loadFloatSample(sampleFile); - SampleGrainFarm sampleGrainFarm = new SampleGrainFarm(); - synth.add(ramp = new ContinuousRamp()); - sampleGrainFarm.setSample(sample); - ramp.output.connect(sampleGrainFarm.position); - grainFarm = sampleGrainFarm; - } else { - GrainFarm sampleGrainFarm = new GrainFarm(); - grainFarm = sampleGrainFarm; - } - - synth.add(grainFarm); - - grainFarm.allocate(NUM_GRAINS); - - // Add an output so we can hear the grains. - synth.add(lineOut = new LineOut()); - - grainFarm.getOutput().connect(0, lineOut.input, 0); - grainFarm.getOutput().connect(0, lineOut.input, 1); - - // Start synthesizer using default stereo output at 44100 Hz. - synth.start(); - - if (useRecorder) { - grainFarm.output.connect(0, recorder.getInput(), 0); - // When we start the recorder it will pull data from the - // oscillator - // and sweeper. - recorder.start(); - } - - setupGUI(); - // We only need to start the LineOut. It will pull data from the - // oscillator. - lineOut.start(); - } catch (IOException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - - } - - @Override - public void stop() { - try { - if (recorder != null) { - recorder.stop(); - recorder.close(); - } - } catch (IOException e) { - e.printStackTrace(); - } - scope.stop(); - synth.stop(); - } - -} diff --git a/tests/com/jsyn/examples/PlayMIDI.java b/tests/com/jsyn/examples/PlayMIDI.java deleted file mode 100644 index 04c6b9b..0000000 --- a/tests/com/jsyn/examples/PlayMIDI.java +++ /dev/null @@ -1,241 +0,0 @@ -/* - * Copyright 2010 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.examples; - -import java.io.IOException; - -import com.jsyn.JSyn; -import com.jsyn.Synthesizer; -import com.jsyn.devices.javasound.MidiDeviceTools; -import com.jsyn.instruments.DualOscillatorSynthVoice; -import com.jsyn.instruments.SubtractiveSynthVoice; -import com.jsyn.midi.MessageParser; -import com.jsyn.midi.MidiConstants; -import com.jsyn.midi.MidiSynthesizer; -import com.jsyn.unitgen.LineOut; -import com.jsyn.unitgen.PowerOfTwo; -import com.jsyn.unitgen.SineOscillator; -import com.jsyn.unitgen.UnitOscillator; -import com.jsyn.unitgen.UnitVoice; -import com.jsyn.util.MultiChannelSynthesizer; -import com.jsyn.util.VoiceAllocator; -import com.jsyn.util.VoiceDescription; -import com.softsynth.math.AudioMath; -import com.softsynth.shared.time.TimeStamp; - -/** - * Send MIDI messages to JSyn based MIDI synthesizer. - * - * @author Phil Burk (C) 2010 Mobileer Inc - */ -public class PlayMIDI { - private static final int NUM_CHANNELS = 16; - private static final int VOICES_PER_CHANNEL = 6; - private Synthesizer synth; - private MidiSynthesizer midiSynthesizer; - private LineOut lineOut; - - private VoiceDescription voiceDescription; - private MultiChannelSynthesizer multiSynth; - - public static void main(String[] args) { - PlayMIDI app = new PlayMIDI(); - try { - VoiceDescription description = DualOscillatorSynthVoice.getVoiceDescription(); - app.test(description); - System.out.println("Test complete"); - } catch (IOException e) { - e.printStackTrace(); - } catch (InterruptedException e) { - e.printStackTrace(); - } - System.exit(0); - } - - public void sendMidiMessage(byte[] bytes) { - midiSynthesizer.onReceive(bytes, 0, bytes.length); - } - - public void sendNoteOff(int channel, int pitch, int velocity) { - midiCommand(MidiConstants.NOTE_OFF + channel, pitch, velocity); - } - - public void sendNoteOn(int channel, int pitch, int velocity) { - midiCommand(MidiConstants.NOTE_ON + channel, pitch, velocity); - } - - public void sendControlChange(int channel, int index, int value) { - midiCommand(MidiConstants.CONTROL_CHANGE + channel, index, value); - } - - /** - * @param channel - * @param program starts at zero - */ - private void sendProgramChange(int channel, int program) { - midiCommand(MidiConstants.PROGRAM_CHANGE + channel, program); - - } - - /** - * Send either RPN or NRPN. - */ - public void sendParameter(int channel, int index14, int value14, int controllerXPN) { - int indexLsb = index14 & 0x07F; - int indexMsb = (index14 >> 7) & 0x07F; - int valueLsb = value14 & 0x07F; - int valueMsb = (value14 >> 7) & 0x07F; - sendControlChange(channel, controllerXPN + 1, indexMsb); - sendControlChange(channel, controllerXPN, indexLsb); - sendControlChange(channel, MidiConstants.CONTROLLER_DATA_ENTRY, valueMsb); - sendControlChange(channel, MidiConstants.CONTROLLER_DATA_ENTRY_LSB, valueLsb); - sendControlChange(channel, controllerXPN + 1, 0x7F); // NULL RPN index - sendControlChange(channel, controllerXPN, 0x7F); // to deactivate RPN - } - - public void sendRPN(int channel, int index14, int value14) { - sendParameter(channel, index14, value14, MidiConstants.CONTROLLER_RPN_LSB); - } - - public void sendNRPN(int channel, int index14, int value14) { - sendParameter(channel, index14, value14, MidiConstants.CONTROLLER_NRPN_LSB); - } - - private void midiCommand(int status, int data1, int data2) { - byte[] buffer = new byte[3]; - buffer[0] = (byte) status; - buffer[1] = (byte) data1; - buffer[2] = (byte) data2; - sendMidiMessage(buffer); - } - - private void midiCommand(int status, int data1) { - byte[] buffer = new byte[2]; - buffer[0] = (byte) status; - buffer[1] = (byte) data1; - sendMidiMessage(buffer); - } - - public int test(VoiceDescription description) throws IOException, InterruptedException { - setupSynth(description); - - //playOctaveUsingBend(); - playSameNotesBent(); - - // Setup all the channels. - int maxChannels = 8; - for (int channel = 0; channel < maxChannels; channel++) { - int program = channel; - sendProgramChange(channel, program); - } - playNotePerChannel(maxChannels); - - return 0; - } - - private void playOctaveUsingBend() throws InterruptedException { - sendProgramChange(0, 0); - float range0 = 12.0f; - sendPitchBendRange(0, range0); - for(int i = 0; i < 13; i++) { - System.out.println("Bend to pitch " + i); - sendPitchBend(0, i / range0); - sendNoteOn(0, 60, 100); - synth.sleepFor(0.5); - sendNoteOff(0, 60, 100); - synth.sleepFor(0.5); - } - } - - private void playSameNotesBent() throws InterruptedException { - sendProgramChange(0, 0); - sendProgramChange(1, 0); - float range0 = 2.3f; - float range1 = 6.8f; - sendPitchBendRange(0, range0); - sendPitchBendRange(1, range1); - sendPitchBend(0, 0.0f / range0); // bend by 0 semitones - sendPitchBend(1, 1.0f / range1); // bend by 1 semitones - - System.out.println("These two notes should play at the same pitch."); - sendNoteOn(0, 61, 100); - synth.sleepFor(0.5); - sendNoteOff(0, 61, 100); - - sendNoteOn(1, 60, 100); - synth.sleepFor(0.5); - sendNoteOff(1, 60, 100); - - synth.sleepFor(2.0); - System.out.println("------ done ---------------"); - } - - /** - * - * @param channel - * @param normalizedBend between -1 and +1 - */ - private void sendPitchBend(int channel, float normalizedBend) { - final int BEND_MIN = 0x0000; - final int BEND_CENTER = 0x2000; - final int BEND_MAX = 0x3FFF; - int bend = BEND_CENTER + (int)(BEND_CENTER * normalizedBend); - if (bend < BEND_MIN) bend = BEND_MIN; - else if (bend > BEND_MAX) bend = BEND_MAX; - int lsb = bend & 0x07F; - int msb = (bend >> 7) & 0x07F; - midiCommand(MidiConstants.PITCH_BEND + channel, lsb, msb); - } - - private void sendPitchBendRange(int channel, float range0) { - int semitones = (int)range0; - int cents = (int) (100 * (range0 - semitones)); - int value = (semitones << 7) + cents; - sendRPN(channel, MidiConstants.RPN_BEND_RANGE, value); - } - - private void playNotePerChannel(int maxChannels) throws InterruptedException { - // Play notes on those channels. - for (int channel = 0; channel < maxChannels; channel++) { - sendNoteOn(channel, 60 + channel, 100); - synth.sleepFor(0.5); - sendNoteOff(channel, 60 + channel, 100); - synth.sleepFor(0.5); - } - } - - private void setupSynth(VoiceDescription description) { - synth = JSyn.createSynthesizer(); - - // Add an output. - synth.add(lineOut = new LineOut()); - - voiceDescription = description; - multiSynth = new MultiChannelSynthesizer(); - final int startChannel = 0; - multiSynth.setup(synth, startChannel, NUM_CHANNELS, VOICES_PER_CHANNEL, voiceDescription); - midiSynthesizer = new MidiSynthesizer(multiSynth); - - multiSynth.getOutput().connect(0,lineOut.input, 0); - multiSynth.getOutput().connect(1,lineOut.input, 1); - - // Start synthesizer using default stereo output at 44100 Hz. - synth.start(); - lineOut.start(); - } - -} diff --git a/tests/com/jsyn/examples/PlayNotes.java b/tests/com/jsyn/examples/PlayNotes.java deleted file mode 100644 index 65dc930..0000000 --- a/tests/com/jsyn/examples/PlayNotes.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * 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.examples; - -import com.jsyn.JSyn; -import com.jsyn.Synthesizer; -import com.jsyn.unitgen.LineOut; -import com.jsyn.unitgen.SawtoothOscillator; -import com.jsyn.unitgen.UnitGenerator; -import com.jsyn.unitgen.UnitVoice; -import com.softsynth.shared.time.TimeStamp; - -/** - * Play notes using timestamped noteOn and noteOff methods of the UnitVoice. - * - * @author Phil Burk (C) 2009 Mobileer Inc - */ -public class PlayNotes { - Synthesizer synth; - UnitGenerator ugen; - UnitVoice voice; - LineOut lineOut; - - private void test() { - // Create a context for the synthesizer. - synth = JSyn.createSynthesizer(); - // Set output latency to 123 msec because this is not an interactive app. - synth.getAudioDeviceManager().setSuggestedOutputLatency(0.123); - - // Add a tone generator. - synth.add(ugen = new SawtoothOscillator()); - // synth.add( ugen = new SineOscillator() ); - // synth.add( ugen = new SubtractiveSynthVoice() ); - voice = (UnitVoice) ugen; - // Add an output mixer. - synth.add(lineOut = new LineOut()); - - // Connect the oscillator to the left and right audio output. - voice.getOutput().connect(0, lineOut.input, 0); - voice.getOutput().connect(0, lineOut.input, 1); - - // Start synthesizer using default stereo output at 44100 Hz. - synth.start(); - - // Get synthesizer time in seconds. - double timeNow = synth.getCurrentTime(); - - // Advance to a near future time so we have a clean start. - TimeStamp timeStamp = new TimeStamp(timeNow + 0.5); - - // We only need to start the LineOut. It will pull data from the - // oscillator. - synth.startUnit(lineOut, timeStamp); - - // Schedule a note on and off. - double freq = 200.0; // hertz - double duration = 1.4; - double onTime = 1.0; - voice.noteOn(freq, 0.5, timeStamp); - voice.noteOff(timeStamp.makeRelative(onTime)); - - // Schedule this to happen a bit later. - timeStamp = timeStamp.makeRelative(duration); - freq *= 1.5; // up a perfect fifth - voice.noteOn(freq, 0.5, timeStamp); - voice.noteOff(timeStamp.makeRelative(onTime)); - - timeStamp = timeStamp.makeRelative(duration); - freq *= 4.0 / 5.0; // down a major third - voice.noteOn(freq, 0.5, timeStamp); - voice.noteOff(timeStamp.makeRelative(onTime)); - - // Sleep while the song is being generated in the background thread. - try { - System.out.println("Sleep while synthesizing."); - synth.sleepUntil(timeStamp.getTime() + 2.0); - System.out.println("Woke up..."); - } catch (InterruptedException e) { - e.printStackTrace(); - } - - // Stop everything. - synth.stop(); - } - - public static void main(String[] args) { - new PlayNotes().test(); - } -} diff --git a/tests/com/jsyn/examples/PlayPartials.java b/tests/com/jsyn/examples/PlayPartials.java deleted file mode 100644 index 1d7d88e..0000000 --- a/tests/com/jsyn/examples/PlayPartials.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright 2010 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.examples; - -import com.jsyn.JSyn; -import com.jsyn.Synthesizer; -import com.jsyn.unitgen.LineOut; -import com.jsyn.unitgen.LinearRamp; -import com.jsyn.unitgen.Multiply; -import com.jsyn.unitgen.SineOscillator; -import com.jsyn.unitgen.UnitOscillator; - -/** - * Play a enharmonic sine tones using JSyn oscillators. - * - * @author Phil Burk (C) 2010 Mobileer Inc - */ -public class PlayPartials { - private Synthesizer synth; - private UnitOscillator[] osc; - private Multiply[] multipliers; - private LinearRamp ramp; - private LineOut lineOut; - private double[] amps = { - 0.2, 0.1, 0.3, 0.4 - }; - private double[] ratios = { - 1.0, Math.sqrt(2.0), Math.E, Math.PI - }; - - private void test() { - // Create a context for the synthesizer. - synth = JSyn.createSynthesizer(); - - // Start synthesizer using default stereo output at 44100 Hz. - synth.start(); - - // Add a stereo audio output unit. - synth.add(lineOut = new LineOut()); - synth.add(ramp = new LinearRamp()); - - // Add a tone generator. - osc = new SineOscillator[amps.length]; - multipliers = new Multiply[ratios.length]; - - for (int i = 0; i < osc.length; i++) { - // Create unit generators and store them in arrays. - synth.add(osc[i] = new SineOscillator()); - synth.add(multipliers[i] = new Multiply()); - - // Connect each oscillator to both channels of the output. - // They will be mixed automatically. - osc[i].output.connect(0, lineOut.input, 0); - osc[i].output.connect(0, lineOut.input, 1); - - // Use a multiplier to scale the output of the ramp. - // output = inputA * inputB - ramp.output.connect(multipliers[i].inputA); - multipliers[i].output.connect(osc[i].frequency); - multipliers[i].inputB.set(ratios[i]); - - osc[i].amplitude.set(amps[i]); - } - - // start ramping up - ramp.current.set(100.0); - ramp.time.set(3.0); - ramp.input.set(700.0); - - // We only need to start the LineOut. It will pull data from the - // oscillator. - lineOut.start(); - - System.out.println("You should now be hearing a sine wave. ---------"); - - // Sleep while the sound is generated in the background. - try { - // Sleep for a few seconds. - synth.sleepFor(4.0); - // ramp down - ramp.input.set(100.0); - synth.sleepFor(4.0); - } catch (InterruptedException e) { - e.printStackTrace(); - } - - System.out.println("Stop playing. -------------------"); - // Stop everything. - synth.stop(); - } - - public static void main(String[] args) { - System.out.println("Java version = " + System.getProperty("java.version")); - new PlayPartials().test(); - } -} diff --git a/tests/com/jsyn/examples/PlaySample.java b/tests/com/jsyn/examples/PlaySample.java deleted file mode 100644 index ac3d5ff..0000000 --- a/tests/com/jsyn/examples/PlaySample.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright 2010 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.examples; - -import java.io.IOException; -import java.net.MalformedURLException; -import java.net.URL; - -import com.jsyn.JSyn; -import com.jsyn.Synthesizer; -import com.jsyn.data.FloatSample; -import com.jsyn.unitgen.LineOut; -import com.jsyn.unitgen.VariableRateDataReader; -import com.jsyn.unitgen.VariableRateMonoReader; -import com.jsyn.unitgen.VariableRateStereoReader; -import com.jsyn.util.SampleLoader; - -/** - * Play a sample from a WAV file using JSyn. - * - * @author Phil Burk (C) 2010 Mobileer Inc - */ -public class PlaySample { - private Synthesizer synth; - private VariableRateDataReader samplePlayer; - private LineOut lineOut; - - private void test() { - - URL sampleFile; - try { - sampleFile = new URL("http://www.softsynth.com/samples/Clarinet.wav"); - // sampleFile = new URL("http://www.softsynth.com/samples/NotHereNow22K.wav"); - } catch (MalformedURLException e2) { - e2.printStackTrace(); - return; - } - - synth = JSyn.createSynthesizer(); - - FloatSample sample; - try { - // Add an output mixer. - synth.add(lineOut = new LineOut()); - - // Load the sample and display its properties. - SampleLoader.setJavaSoundPreferred(false); - sample = SampleLoader.loadFloatSample(sampleFile); - System.out.println("Sample has: channels = " + sample.getChannelsPerFrame()); - System.out.println(" frames = " + sample.getNumFrames()); - System.out.println(" rate = " + sample.getFrameRate()); - System.out.println(" loopStart = " + sample.getSustainBegin()); - System.out.println(" loopEnd = " + sample.getSustainEnd()); - - if (sample.getChannelsPerFrame() == 1) { - synth.add(samplePlayer = new VariableRateMonoReader()); - samplePlayer.output.connect(0, lineOut.input, 0); - } else if (sample.getChannelsPerFrame() == 2) { - synth.add(samplePlayer = new VariableRateStereoReader()); - samplePlayer.output.connect(0, lineOut.input, 0); - samplePlayer.output.connect(1, lineOut.input, 1); - } else { - throw new RuntimeException("Can only play mono or stereo samples."); - } - - // Start synthesizer using default stereo output at 44100 Hz. - synth.start(); - - samplePlayer.rate.set(sample.getFrameRate()); - - // We only need to start the LineOut. It will pull data from the - // sample player. - lineOut.start(); - - // We can simply queue the entire file. - // Or if it has a loop we can play the loop for a while. - if (sample.getSustainBegin() < 0) { - System.out.println("queue the sample"); - samplePlayer.dataQueue.queue(sample); - } else { - System.out.println("queueOn the sample"); - samplePlayer.dataQueue.queueOn(sample); - synth.sleepFor(8.0); - System.out.println("queueOff the sample"); - samplePlayer.dataQueue.queueOff(sample); - } - - // Wait until the sample has finished playing. - do { - synth.sleepFor(1.0); - } while (samplePlayer.dataQueue.hasMore()); - - synth.sleepFor(0.5); - - } catch (IOException e1) { - e1.printStackTrace(); - } catch (InterruptedException e) { - e.printStackTrace(); - } - // Stop everything. - synth.stop(); - } - - public static void main(String[] args) { - new PlaySample().test(); - } -} diff --git a/tests/com/jsyn/examples/PlaySampleCrossfade.java b/tests/com/jsyn/examples/PlaySampleCrossfade.java deleted file mode 100644 index b5ea5ca..0000000 --- a/tests/com/jsyn/examples/PlaySampleCrossfade.java +++ /dev/null @@ -1,183 +0,0 @@ -/* - * Copyright 2010 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.examples; - -import java.awt.GridLayout; -import java.io.IOException; -import java.net.MalformedURLException; -import java.net.URL; - -import javax.swing.JApplet; -import javax.swing.event.ChangeEvent; -import javax.swing.event.ChangeListener; - -import com.jsyn.JSyn; -import com.jsyn.Synthesizer; -import com.jsyn.data.FloatSample; -import com.jsyn.devices.AudioDeviceFactory; -import com.jsyn.ports.QueueDataCommand; -import com.jsyn.swing.DoubleBoundedRangeModel; -import com.jsyn.swing.DoubleBoundedRangeSlider; -import com.jsyn.swing.JAppletFrame; -import com.jsyn.swing.PortControllerFactory; -import com.jsyn.unitgen.LineOut; -import com.jsyn.unitgen.VariableRateDataReader; -import com.jsyn.unitgen.VariableRateMonoReader; -import com.jsyn.unitgen.VariableRateStereoReader; -import com.jsyn.util.SampleLoader; - -/** - * Play a sample from a WAV file using JSyn. Use a crossfade to play a loop at an arbitrary - * position. - * - * @author Phil Burk (C) 2010 Mobileer Inc - */ -public class PlaySampleCrossfade extends JApplet { - private static final double LOOP_START_FRACTION = 0.2; - private Synthesizer synth; - private VariableRateDataReader samplePlayer; - private LineOut lineOut; - private FloatSample sample; - private DoubleBoundedRangeModel rangeModelSize; - private DoubleBoundedRangeModel rangeModelCrossfade; - private int loopStartFrame; - - @Override - public void init() { - - URL sampleFile; - try { - sampleFile = new URL("http://www.softsynth.com/samples/Clarinet.wav"); - } catch (MalformedURLException e2) { - e2.printStackTrace(); - return; - } - - synth = JSyn.createSynthesizer(AudioDeviceFactory.createAudioDeviceManager(true)); - - try { - // Add an output mixer. - synth.add(lineOut = new LineOut()); - - // Load the sample and display its properties. - SampleLoader.setJavaSoundPreferred(false); - sample = SampleLoader.loadFloatSample(sampleFile); - System.out.println("Sample has: channels = " + sample.getChannelsPerFrame()); - System.out.println(" frames = " + sample.getNumFrames()); - System.out.println(" rate = " + sample.getFrameRate()); - System.out.println(" loopStart = " + sample.getSustainBegin()); - System.out.println(" loopEnd = " + sample.getSustainEnd()); - - if (sample.getChannelsPerFrame() == 1) { - synth.add(samplePlayer = new VariableRateMonoReader()); - samplePlayer.output.connect(0, lineOut.input, 0); - } else if (sample.getChannelsPerFrame() == 2) { - synth.add(samplePlayer = new VariableRateStereoReader()); - samplePlayer.output.connect(0, lineOut.input, 0); - samplePlayer.output.connect(1, lineOut.input, 1); - } else { - throw new RuntimeException("Can only play mono or stereo samples."); - } - - samplePlayer.rate.set(sample.getFrameRate()); - - } catch (IOException e1) { - e1.printStackTrace(); - } - - // Start at arbitrary position near beginning of sample. - loopStartFrame = (int) (sample.getNumFrames() * LOOP_START_FRACTION); - - // Arrange the faders in a stack. - setLayout(new GridLayout(0, 1)); - - samplePlayer.rate.setup(4000.0, sample.getFrameRate(), sample.getFrameRate() * 2.0); - add(PortControllerFactory.createExponentialPortSlider(samplePlayer.rate)); - - // Use fader to select arbitrary loop size. - rangeModelSize = new DoubleBoundedRangeModel("LoopSize", 10000, 0.01, - (1.0 - LOOP_START_FRACTION), 0.5); - rangeModelSize.addChangeListener(new ChangeListener() { - @Override - public void stateChanged(ChangeEvent e) { - queueNewLoop(); - } - - }); - add(new DoubleBoundedRangeSlider(rangeModelSize, 3)); - - // Use fader to set the size of the crossfade region. - rangeModelCrossfade = new DoubleBoundedRangeModel("Crossfade", 1000, 0.0, 1000.0, 0.0); - rangeModelCrossfade.addChangeListener(new ChangeListener() { - @Override - public void stateChanged(ChangeEvent e) { - queueNewLoop(); - } - - }); - - add(new DoubleBoundedRangeSlider(rangeModelCrossfade, 3)); - - validate(); - } - - private void queueNewLoop() { - int loopSize = (int) (sample.getNumFrames() * rangeModelSize.getDoubleValue()); - if ((loopStartFrame + loopSize) > sample.getNumFrames()) { - loopSize = sample.getNumFrames() - loopStartFrame; - } - int crossFadeSize = (int) (rangeModelCrossfade.getDoubleValue()); - - // For complex queuing operations, create a command and then customize it. - QueueDataCommand command = samplePlayer.dataQueue.createQueueDataCommand(sample, - loopStartFrame, loopSize); - command.setNumLoops(-1); - command.setSkipIfOthers(true); - command.setCrossFadeIn(crossFadeSize); - - System.out.println("Queue: " + loopStartFrame + ", #" + loopSize + ", X=" + crossFadeSize); - synth.queueCommand(command); - } - - @Override - public void start() { - // Start synthesizer using default stereo output at 44100 Hz. - synth.start(); - // Start the LineOut. It will pull data from the oscillator. - lineOut.start(); - - // Queue attack portion of sample. - samplePlayer.dataQueue.queue(sample, 0, loopStartFrame); - queueNewLoop(); - } - - @Override - public void stop() { - synth.stop(); - synth.stop(); - } - - /* Can be run as either an application or as an applet. */ - public static void main(String args[]) { - PlaySampleCrossfade applet = new PlaySampleCrossfade(); - JAppletFrame frame = new JAppletFrame("PlaySampleCrossfade", applet); - frame.setSize(440, 300); - frame.setVisible(true); - frame.test(); - } - -} diff --git a/tests/com/jsyn/examples/PlaySampleWaveShaper.java b/tests/com/jsyn/examples/PlaySampleWaveShaper.java deleted file mode 100644 index 73758dd..0000000 --- a/tests/com/jsyn/examples/PlaySampleWaveShaper.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright 2010 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.examples; - -import java.io.IOException; -import java.net.MalformedURLException; -import java.net.URL; - -import com.jsyn.JSyn; -import com.jsyn.Synthesizer; -import com.jsyn.data.FloatSample; -import com.jsyn.data.Function; -import com.jsyn.unitgen.FunctionEvaluator; -import com.jsyn.unitgen.LineOut; -import com.jsyn.unitgen.SineOscillator; -import com.jsyn.util.SampleLoader; - -/** - * Play a sample from a WAV file using JSyn. - * - * @author Phil Burk (C) 2010 Mobileer Inc - */ -public class PlaySampleWaveShaper { - private Synthesizer synth; - private LineOut lineOut; - - private void test() { - - URL sampleFile; - try { - sampleFile = new URL("http://www.softsynth.com/samples/Clarinet.wav"); - // sampleFile = new URL("http://www.softsynth.com/samples/NotHereNow22K.wav"); - } catch (MalformedURLException e2) { - e2.printStackTrace(); - return; - } - - synth = JSyn.createSynthesizer(); - - FloatSample sample; - try { - // Add an output mixer. - synth.add(lineOut = new LineOut()); - - // Load the sample and display its properties. - SampleLoader.setJavaSoundPreferred(false); - sample = SampleLoader.loadFloatSample(sampleFile); - System.out.println("Sample has: channels = " + sample.getChannelsPerFrame()); - System.out.println(" frames = " + sample.getNumFrames()); - System.out.println(" rate = " + sample.getFrameRate()); - System.out.println(" loopStart = " + sample.getSustainBegin()); - System.out.println(" loopEnd = " + sample.getSustainEnd()); - - if (sample.getChannelsPerFrame() != 1) { - throw new RuntimeException("Can only use mono samples."); - } - - System.out.println("eval -1.1 = " + sample.evaluate(-1.1)); - System.out.println("eval -1.0 = " + sample.evaluate(-1.0)); - System.out.println("eval 0.3 = " + sample.evaluate(0.3)); - System.out.println("eval 1.0 = " + sample.evaluate(1.0)); - System.out.println("eval 1.1 = " + sample.evaluate(1.1)); - - FunctionEvaluator shaper = new FunctionEvaluator(); - shaper.function.set(sample); - synth.add(shaper); - - shaper.output.connect(0, lineOut.input, 0); - shaper.output.connect(0, lineOut.input, 1); - - SineOscillator osc = new SineOscillator(); - osc.frequency.set(0.2); - osc.output.connect(shaper.input); - synth.add(osc); - - // Start synthesizer using default stereo output at 44100 Hz. - synth.start(); - - // We only need to start the LineOut. It will pull data from the - // sample player. - lineOut.start(); - - // Wait until the sample has finished playing. - synth.sleepFor(5.0); - - } catch (IOException e1) { - e1.printStackTrace(); - } catch (InterruptedException e) { - e.printStackTrace(); - } - // Stop everything. - synth.stop(); - } - - public static void main(String[] args) { - new PlaySampleWaveShaper().test(); - } -} diff --git a/tests/com/jsyn/examples/PlaySegmentedEnvelope.java b/tests/com/jsyn/examples/PlaySegmentedEnvelope.java deleted file mode 100644 index e7cc8f7..0000000 --- a/tests/com/jsyn/examples/PlaySegmentedEnvelope.java +++ /dev/null @@ -1,151 +0,0 @@ -/* - * Copyright 2010 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.examples; - -import java.io.File; -import java.io.IOException; - -import com.jsyn.JSyn; -import com.jsyn.Synthesizer; -import com.jsyn.data.SegmentedEnvelope; -import com.jsyn.unitgen.LineOut; -import com.jsyn.unitgen.SawtoothOscillatorBL; -import com.jsyn.unitgen.UnitOscillator; -import com.jsyn.unitgen.VariableRateDataReader; -import com.jsyn.unitgen.VariableRateMonoReader; -import com.jsyn.util.WaveRecorder; - -/** - * Modulate the amplitude of an oscillator using a segmented envelope. - * - * @author Phil Burk (C) 2010 Mobileer Inc - */ -public class PlaySegmentedEnvelope { - private Synthesizer synth; - private UnitOscillator osc; - private LineOut lineOut; - private SegmentedEnvelope envelope; - private VariableRateDataReader envelopePlayer; - private WaveRecorder recorder; - private final static boolean useRecorder = true; - - private void test() throws IOException { - // Create a context for the synthesizer. - synth = JSyn.createSynthesizer(); - // Add a tone generator. - synth.add(osc = new SawtoothOscillatorBL()); - // Add an envelope player. - synth.add(envelopePlayer = new VariableRateMonoReader()); - - if (useRecorder) { - File waveFile = new File("temp_recording.wav"); - // Default is stereo, 16 bits. - recorder = new WaveRecorder(synth, waveFile); - System.out.println("Writing to WAV file " + waveFile.getAbsolutePath()); - } - - // Create an envelope consisting of (duration,value) pairs. - double[] pairs = { - 0.1, 1.0, 0.2, 0.3, 0.6, 0.0 - }; - envelope = new SegmentedEnvelope(pairs); - - // Add an output mixer. - synth.add(lineOut = new LineOut()); - envelopePlayer.output.connect(osc.amplitude); - // Connect the oscillator to the output. - osc.output.connect(0, lineOut.input, 0); - osc.output.connect(0, lineOut.input, 1); - - // Start synthesizer using default stereo output at 44100 Hz. - synth.start(); - - if (useRecorder) { - osc.output.connect(0, recorder.getInput(), 0); - envelopePlayer.output.connect(0, recorder.getInput(), 1); - // When we start the recorder it will pull data from the oscillator - // and sweeper. - recorder.start(); - } - - // We only need to start the LineOut. It will pull data from the other - // units. - lineOut.start(); - - try { - // --------------------------------------------- - // Queue the entire envelope to play once. - envelopePlayer.dataQueue.queue(envelope); - synth.sleepFor(2.0); - - // --------------------------------------------- - // Queue the attack, then sustain for a while, then queue the - // release. - osc.frequency.set(750.0); - envelopePlayer.dataQueue.queue(envelope, 0, 2); // attack - synth.sleepFor(2.0); - envelopePlayer.dataQueue.queue(envelope, 2, 1); // release - synth.sleepFor(2.0); - - // --------------------------------------------- - // Queue the attack, then sustain for a while, then queue the - // release. But this time use the sustain loop points. - osc.frequency.set(950.0); - // For noteOn, we want to play frames 0 and 1 then stop before 2. - envelope.setSustainBegin(2); - envelope.setSustainEnd(2); - envelopePlayer.dataQueue.queueOn(envelope); // attack - synth.sleepFor(2.0); - envelopePlayer.dataQueue.queueOff(envelope); // release - synth.sleepFor(2.0); - - // --------------------------------------------- - // Queue the entire envelope to play 4 times (3 loops back). - osc.frequency.set(350.0); - envelopePlayer.dataQueue.queueLoop(envelope, 0, envelope.getNumFrames(), 3); - synth.sleepFor(5.0); - - // --------------------------------------------- - // Queue the entire envelope as a repeating loop. - // It will loop until something else is queued. - osc.frequency.set(450.0); - envelopePlayer.dataQueue.queueLoop(envelope, 0, envelope.getNumFrames()); - envelopePlayer.rate.set(3.0); - synth.sleepFor(5.0); - // Queue last frame to stop the looping. - envelopePlayer.dataQueue.queue(envelope, envelope.getNumFrames() - 1, 1); - synth.sleepFor(1.0); - - if (recorder != null) { - recorder.stop(); - recorder.close(); - } - } catch (InterruptedException e) { - e.printStackTrace(); - } - // Stop everything. - synth.stop(); - } - - public static void main(String[] args) { - try { - new PlaySegmentedEnvelope().test(); - } catch (IOException e) { - e.printStackTrace(); - } - } -} diff --git a/tests/com/jsyn/examples/PlaySegmentedEnvelopeCallback.java b/tests/com/jsyn/examples/PlaySegmentedEnvelopeCallback.java deleted file mode 100644 index cf2441e..0000000 --- a/tests/com/jsyn/examples/PlaySegmentedEnvelopeCallback.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright 2010 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.examples; - -import com.jsyn.JSyn; -import com.jsyn.Synthesizer; -import com.jsyn.data.SegmentedEnvelope; -import com.jsyn.ports.QueueDataCommand; -import com.jsyn.ports.QueueDataEvent; -import com.jsyn.ports.UnitDataQueueCallback; -import com.jsyn.unitgen.LineOut; -import com.jsyn.unitgen.SawtoothOscillatorBL; -import com.jsyn.unitgen.UnitOscillator; -import com.jsyn.unitgen.VariableRateDataReader; -import com.jsyn.unitgen.VariableRateMonoReader; - -/** - * Use a UnitDataQueueCallback to notify us of the envelope's progress. Modulate the amplitude of an - * oscillator using a segmented envelope. - * - * @author Phil Burk (C) 2010 Mobileer Inc - */ -public class PlaySegmentedEnvelopeCallback { - private Synthesizer synth; - private UnitOscillator osc; - private LineOut lineOut; - private SegmentedEnvelope envelope; - private VariableRateDataReader envelopePlayer; - - private void test() { - // Create a context for the synthesizer. - synth = JSyn.createSynthesizer(); - // Add a tone generator. - synth.add(osc = new SawtoothOscillatorBL()); - // Add an envelope player. - synth.add(envelopePlayer = new VariableRateMonoReader()); - - // Create an envelope consisting of (duration,value) pairs. - double[] pairs = { - 0.1, 1.0, 0.2, 0.5, 0.6, 0.0 - }; - envelope = new SegmentedEnvelope(pairs); - - // Add an output mixer. - synth.add(lineOut = new LineOut()); - envelopePlayer.output.connect(osc.amplitude); - // Connect the oscillator to the output. - osc.output.connect(0, lineOut.input, 0); - osc.output.connect(0, lineOut.input, 1); - - // Start synthesizer using default stereo output at 44100 Hz. - synth.start(); - // We only need to start the LineOut. It will pull data from the other - // units. - lineOut.start(); - - try { - // Queue an envelope with callbacks. - QueueDataCommand command = envelopePlayer.dataQueue.createQueueDataCommand(envelope, 0, - envelope.getNumFrames()); - // Create an object to be called when the queued data is done. - TestQueueCallback callback = new TestQueueCallback(); - command.setCallback(callback); - command.setNumLoops(2); - envelopePlayer.rate.set(0.2); - synth.queueCommand(command); - synth.sleepFor(20.0); - - } catch (InterruptedException e) { - e.printStackTrace(); - } - // Stop everything. - synth.stop(); - } - - class TestQueueCallback implements UnitDataQueueCallback { - @Override - public void started(QueueDataEvent event) { - System.out.println("CALLBACK: Envelope started."); - } - - @Override - public void looped(QueueDataEvent event) { - System.out.println("CALLBACK: Envelope looped."); - } - - @Override - public void finished(QueueDataEvent event) { - System.out.println("CALLBACK: Envelope finished."); - // Queue the envelope again at a faster rate. - // (If this hangs we may have hit a deadlock.) - envelopePlayer.rate.set(2.0); - envelopePlayer.dataQueue.queue(envelope); - } - } - - public static void main(String[] args) { - new PlaySegmentedEnvelopeCallback().test(); - } -} diff --git a/tests/com/jsyn/examples/PlaySequence.java b/tests/com/jsyn/examples/PlaySequence.java deleted file mode 100644 index 9d058b2..0000000 --- a/tests/com/jsyn/examples/PlaySequence.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * 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.examples; - -import com.jsyn.JSyn; -import com.jsyn.Synthesizer; -import com.jsyn.unitgen.LineOut; -import com.jsyn.unitgen.SawtoothOscillatorBL; -import com.jsyn.unitgen.UnitOscillator; - -/** - * Use time stamps to change the frequency of an oscillator at precise times. - * - * @author Phil Burk (C) 2009 Mobileer Inc - */ -public class PlaySequence { - Synthesizer synth; - UnitOscillator osc; - LineOut lineOut; - - private void test() { - // Create a context for the synthesizer. - synth = JSyn.createSynthesizer(); - - // Add a tone generator. - synth.add(osc = new SawtoothOscillatorBL()); - // Add an output mixer. - synth.add(lineOut = new LineOut()); - - // Connect the oscillator to the left and right audio output. - osc.output.connect(0, lineOut.input, 0); - osc.output.connect(0, lineOut.input, 1); - - // Start synthesizer using default stereo output at 44100 Hz. - synth.start(); - // We only need to start the LineOut. It will pull data from the - // oscillator. - lineOut.start(); - - // Get synthesizer time in seconds. - double timeNow = synth.getCurrentTime(); - - // Advance to a near future time so we have a clean start. - double time = timeNow + 0.5; - double freq = 400.0; // hertz - osc.frequency.set(freq, time); - - // Schedule this to happen a bit later. - time += 0.5; - freq *= 1.5; // up a perfect fifth - osc.frequency.set(freq, time); - - time += 0.5; - freq *= 4.0 / 5.0; // down a major third - osc.frequency.set(freq, time); - - // Sleep while the sound is being generated in the background thread. - try { - synth.sleepUntil(time + 0.5); - } catch (InterruptedException e) { - e.printStackTrace(); - } - - // Stop everything. - synth.stop(); - } - - public static void main(String[] args) { - new PlaySequence().test(); - } -} diff --git a/tests/com/jsyn/examples/PlayTone.java b/tests/com/jsyn/examples/PlayTone.java deleted file mode 100644 index 172c98a..0000000 --- a/tests/com/jsyn/examples/PlayTone.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2010 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.examples; - -import com.jsyn.JSyn; -import com.jsyn.Synthesizer; -import com.jsyn.unitgen.LineOut; -import com.jsyn.unitgen.SineOscillator; -import com.jsyn.unitgen.UnitOscillator; - -/** - * Play a tone using a JSyn oscillator. - * - * @author Phil Burk (C) 2010 Mobileer Inc - */ -public class PlayTone { - Synthesizer synth; - UnitOscillator osc; - LineOut lineOut; - - private void test() { - - // Create a context for the synthesizer. - synth = JSyn.createSynthesizer(); - - // Start synthesizer using default stereo output at 44100 Hz. - synth.start(); - - // Add a tone generator. - synth.add(osc = new SineOscillator()); - // Add a stereo audio output unit. - synth.add(lineOut = new LineOut()); - - // Connect the oscillator to both channels of the output. - osc.output.connect(0, lineOut.input, 0); - osc.output.connect(0, lineOut.input, 1); - - // Set the frequency and amplitude for the sine wave. - osc.frequency.set(345.0); - osc.amplitude.set(0.6); - - // We only need to start the LineOut. It will pull data from the - // oscillator. - lineOut.start(); - - System.out.println("You should now be hearing a sine wave. ---------"); - - // Sleep while the sound is generated in the background. - try { - double time = synth.getCurrentTime(); - System.out.println("time = " + time); - // Sleep for a few seconds. - synth.sleepUntil(time + 4.0); - } catch (InterruptedException e) { - e.printStackTrace(); - } - - System.out.println("Stop playing. -------------------"); - // Stop everything. - synth.stop(); - } - - public static void main(String[] args) { - new PlayTone().test(); - } -} diff --git a/tests/com/jsyn/examples/RecordSineSweep.java b/tests/com/jsyn/examples/RecordSineSweep.java deleted file mode 100644 index bb248e8..0000000 --- a/tests/com/jsyn/examples/RecordSineSweep.java +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright 2010 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. - */ -/** - * Test recording to disk in non-real-time. - * Play several frequencies of a sine wave. - * Save data in a WAV file format. - * - * @author (C) 2010 Phil Burk - */ - -package com.jsyn.examples; - -import java.io.File; -import java.io.IOException; - -import com.jsyn.JSyn; -import com.jsyn.Synthesizer; -import com.jsyn.unitgen.ExponentialRamp; -import com.jsyn.unitgen.LineOut; -import com.jsyn.unitgen.SawtoothOscillatorBL; -import com.jsyn.unitgen.SineOscillator; -import com.jsyn.unitgen.UnitOscillator; -import com.jsyn.util.WaveRecorder; - -public class RecordSineSweep { - final static double SONG_DURATION = 4.0; - private Synthesizer synth; - private UnitOscillator leftOsc; - private UnitOscillator rightOsc; - private ExponentialRamp sweeper; - private LineOut lineOut; - private WaveRecorder recorder; - private final static boolean useRecorder = true; - - private void test() throws IOException { - // Create a context for the synthesizer. - synth = JSyn.createSynthesizer(); - synth.setRealTime(false); - - if (useRecorder) { - File waveFile = new File("temp_recording.wav"); - // Default is stereo, 16 bits. - recorder = new WaveRecorder(synth, waveFile); - System.out.println("Writing to WAV file " + waveFile.getAbsolutePath()); - } - // Add some tone generators. - synth.add(leftOsc = new SineOscillator()); - synth.add(rightOsc = new SawtoothOscillatorBL()); - - // Add a controller that will sweep up. - synth.add(sweeper = new ExponentialRamp()); - // Add an output unit. - synth.add(lineOut = new LineOut()); - - sweeper.current.set(50.0); - sweeper.input.set(1400.0); - sweeper.time.set(SONG_DURATION); - sweeper.output.connect(leftOsc.frequency); - sweeper.output.connect(rightOsc.frequency); - - // Connect the oscillator to the left and right audio output. - leftOsc.output.connect(0, lineOut.input, 0); - rightOsc.output.connect(0, lineOut.input, 1); - - // Start synthesizer using default stereo output at 44100 Hz. - synth.start(); - - if (useRecorder) { - leftOsc.output.connect(0, recorder.getInput(), 0); - rightOsc.output.connect(0, recorder.getInput(), 1); - // When we start the recorder it will pull data from the oscillator - // and sweeper. - recorder.start(); - } - - // We also need to start the LineOut if we want to hear it now. - lineOut.start(); - - // Get synthesizer time in seconds. - double timeNow = synth.getCurrentTime(); - - // Sleep while the sound is being generated in the background thread. - try { - synth.sleepUntil(timeNow + SONG_DURATION); - } catch (InterruptedException e) { - e.printStackTrace(); - } - - // Test stopping and restarting a recorder. This will cause a pop. - if (recorder != null) { - System.out.println("Stop and restart recorder."); - recorder.stop(); - } - sweeper.input.set(100.0); - timeNow = synth.getCurrentTime(); - if (recorder != null) { - recorder.start(); - } - - // Sleep while the sound is being generated in the background thread. - try { - synth.sleepUntil(timeNow + SONG_DURATION); - } catch (InterruptedException e) { - e.printStackTrace(); - } - - if (recorder != null) { - recorder.stop(); - recorder.close(); - } - // Stop everything. - synth.stop(); - } - - public static void main(String[] args) { - try { - new RecordSineSweep().test(); - } catch (IOException e) { - e.printStackTrace(); - } - } - -} diff --git a/tests/com/jsyn/examples/SampleHoldNoteBlaster.java b/tests/com/jsyn/examples/SampleHoldNoteBlaster.java deleted file mode 100644 index bc6b4d0..0000000 --- a/tests/com/jsyn/examples/SampleHoldNoteBlaster.java +++ /dev/null @@ -1,153 +0,0 @@ -/* - * Copyright 2011 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.examples; - -import com.jsyn.ports.UnitInputPort; -import com.jsyn.ports.UnitOutputPort; -import com.jsyn.unitgen.Circuit; -import com.jsyn.unitgen.EdgeDetector; -import com.jsyn.unitgen.EnvelopeDAHDSR; -import com.jsyn.unitgen.FilterLowPass; -import com.jsyn.unitgen.Latch; -import com.jsyn.unitgen.Multiply; -import com.jsyn.unitgen.PassThrough; -import com.jsyn.unitgen.PulseOscillator; -import com.jsyn.unitgen.SawtoothOscillatorDPW; -import com.jsyn.unitgen.SineOscillator; -import com.jsyn.unitgen.UnitOscillator; -import com.jsyn.unitgen.UnitSource; - -/** - * Classic osc-filter-envelope voice with a sample and hold. - * - * @author Phil Burk (C) 2011 Mobileer Inc - */ -public class SampleHoldNoteBlaster extends Circuit implements UnitSource { - - public UnitInputPort frequency; - public UnitInputPort amplitude; - public UnitInputPort modRate; - public UnitInputPort modDepth; - private UnitInputPort cutoff; - private UnitInputPort resonance; - private UnitInputPort pulseRate; - private UnitInputPort sweepRate; - private UnitInputPort sweepDepth; - public UnitOutputPort output; - - private static SampleHoldNoteBlaster soundMaker; // singleton - - private UnitOscillator osc; - private UnitOscillator samplee; // for sample and hold - private PulseOscillator pulser; - private Latch latch; - private UnitOscillator lfo; - private FilterLowPass filter; - private PassThrough frequencyPin; - private Multiply modScaler; - private EnvelopeDAHDSR ampEnv; - private Multiply sweepScaler; - private EdgeDetector edgeDetector; - private UnitInputPort pulseWidth; - private UnitInputPort attack; - private UnitInputPort decay; - private UnitInputPort sustain; - private UnitInputPort release; - - public static SampleHoldNoteBlaster getInstance() { - if (soundMaker == null) { - soundMaker = new SampleHoldNoteBlaster(); - } - return soundMaker; - } - - public SampleHoldNoteBlaster() { - add(frequencyPin = new PassThrough()); - add(modScaler = new Multiply()); - add(sweepScaler = new Multiply()); - add(edgeDetector = new EdgeDetector()); - add(latch = new Latch()); - add(samplee = new SineOscillator()); - add(pulser = new PulseOscillator()); - add(lfo = new SineOscillator()); - add(osc = new SawtoothOscillatorDPW()); - add(filter = new FilterLowPass()); - // Use an envelope to control the amplitude. - add(ampEnv = new EnvelopeDAHDSR()); - - samplee.output.connect(latch.input); - pulser.output.connect(edgeDetector.input); - edgeDetector.output.connect(latch.gate); - latch.output.connect(osc.frequency); - - frequencyPin.output.connect(osc.frequency); - - frequencyPin.output.connect(modScaler.inputA); - modScaler.output.connect(lfo.amplitude); - - frequencyPin.output.connect(sweepScaler.inputA); - sweepScaler.output.connect(samplee.amplitude); - - lfo.output.connect(osc.frequency); - osc.output.connect(filter.input); - filter.output.connect(ampEnv.amplitude); - pulser.output.connect(ampEnv.input); - - // Setup ports --------------- - addPort(amplitude = osc.amplitude, "amplitude"); - amplitude.set(0.6); - - addPort(frequency = frequencyPin.input, "frequency"); - frequency.setup(50.0, 800.0, 2000.0); - - addPort(modRate = lfo.frequency, "modRate"); - modRate.setup(0.0, 12, 20.0); - - addPort(modDepth = modScaler.inputB, "modDepth"); - modDepth.setup(0.0, 0.0, 0.5); - - addPort(cutoff = filter.frequency, "cutoff"); - cutoff.setup(20.0, 2000.0, 5000.0); - addPort(resonance = filter.Q, "Q"); - - addPort(sweepDepth = sweepScaler.inputB, "sweepDepth"); - sweepDepth.setup(0.0, 0.6, 1.0); - addPort(sweepRate = samplee.frequency, "sweepRate"); - sweepRate.setup(0.2, 5.9271, 20.0); - - addPort(pulseRate = pulser.frequency, "pulseRate"); - pulseRate.setup(0.2, 7.0, 20.0); - addPort(pulseWidth = pulser.width, "pulseWidth"); - pulseWidth.setup(-0.9, 0.9, 0.9); - - addPort(attack = ampEnv.attack, "attack"); - attack.setup(0.001, 0.001, 2.0); - addPort(decay = ampEnv.decay, "decay"); - decay.setup(0.001, 0.26, 2.0); - addPort(sustain = ampEnv.sustain, "sustain"); - sustain.setup(0.000, 0.24, 1.0); - addPort(release = ampEnv.release, "release"); - release.setup(0.001, 0.2, 2.0); - - addPort(output = ampEnv.output); - } - - @Override - public UnitOutputPort getOutput() { - return output; - } -} diff --git a/tests/com/jsyn/examples/SawFaders.java b/tests/com/jsyn/examples/SawFaders.java deleted file mode 100644 index eaa3d1b..0000000 --- a/tests/com/jsyn/examples/SawFaders.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright 2010 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.examples; - -import java.awt.GridLayout; - -import javax.swing.JApplet; -import javax.swing.JPanel; - -import com.jsyn.JSyn; -import com.jsyn.Synthesizer; -import com.jsyn.swing.ExponentialRangeModel; -import com.jsyn.swing.JAppletFrame; -import com.jsyn.swing.PortControllerFactory; -import com.jsyn.swing.PortModelFactory; -import com.jsyn.swing.RotaryTextController; -import com.jsyn.unitgen.LineOut; -import com.jsyn.unitgen.LinearRamp; -import com.jsyn.unitgen.SawtoothOscillatorBL; -import com.jsyn.unitgen.UnitOscillator; - -/** - * Play a sawtooth using a JSyn oscillator and some knobs. - * - * @author Phil Burk (C) 2010 Mobileer Inc - */ -public class SawFaders extends JApplet { - private static final long serialVersionUID = -2704222221111608377L; - private Synthesizer synth; - private UnitOscillator osc; - private LinearRamp lag; - private LineOut lineOut; - - @Override - public void init() { - synth = JSyn.createSynthesizer(); - - // Add a tone generator. (band limited sawtooth) - synth.add(osc = new SawtoothOscillatorBL()); - // Add a lag to smooth out amplitude changes and avoid pops. - synth.add(lag = new LinearRamp()); - // Add an output mixer. - synth.add(lineOut = new LineOut()); - // Connect the oscillator to both left and right output. - osc.output.connect(0, lineOut.input, 0); - osc.output.connect(0, lineOut.input, 1); - - // Set the minimum, current and maximum values for the port. - lag.output.connect(osc.amplitude); - lag.input.setup(0.0, 0.5, 1.0); - lag.time.set(0.2); - - // Arrange the faders in a stack. - setLayout(new GridLayout(0, 1)); - - ExponentialRangeModel amplitudeModel = PortModelFactory.createExponentialModel(lag.input); - RotaryTextController knob = new RotaryTextController(amplitudeModel, 5); - JPanel knobPanel = new JPanel(); - knobPanel.add(knob); - add(knobPanel); - - osc.frequency.setup(50.0, 300.0, 10000.0); - add(PortControllerFactory.createExponentialPortSlider(osc.frequency)); - validate(); - } - - @Override - public void start() { - // Start synthesizer using default stereo output at 44100 Hz. - synth.start(); - // We only need to start the LineOut. It will pull data from the - // oscillator. - lineOut.start(); - } - - @Override - public void stop() { - synth.stop(); - } - - /* Can be run as either an application or as an applet. */ - public static void main(String args[]) { - SawFaders applet = new SawFaders(); - JAppletFrame frame = new JAppletFrame("SawFaders", applet); - frame.setSize(440, 200); - frame.setVisible(true); - frame.test(); - } - -} diff --git a/tests/com/jsyn/examples/SeeGoogleWave.java b/tests/com/jsyn/examples/SeeGoogleWave.java deleted file mode 100644 index eb7a5ff..0000000 --- a/tests/com/jsyn/examples/SeeGoogleWave.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright 2010 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.examples; - -import java.awt.BorderLayout; -import java.awt.GridLayout; - -import javax.swing.JApplet; -import javax.swing.JLabel; -import javax.swing.JPanel; - -import com.jsyn.JSyn; -import com.jsyn.Synthesizer; -import com.jsyn.scope.AudioScope; -import com.jsyn.swing.JAppletFrame; -import com.jsyn.swing.PortControllerFactory; -import com.jsyn.unitgen.LineOut; - -/** - * Generate the waveform shown on the Google home page on 2/22/12. - * - * @author Phil Burk (C) 2010 Mobileer Inc - */ -public class SeeGoogleWave extends JApplet { - private static final long serialVersionUID = -831590388347137926L; - private Synthesizer synth; - private GoogleWaveOscillator googleWaveUnit; - private LineOut lineOut; - private AudioScope scope; - - /* Can be run as either an application or as an applet. */ - public static void main(String args[]) { - SeeGoogleWave applet = new SeeGoogleWave(); - JAppletFrame frame = new JAppletFrame("Google Wave", applet); - frame.setSize(640, 500); - frame.setVisible(true); - frame.test(); - frame.validate(); - } - - private void setupGUI() { - setLayout(new BorderLayout()); - - add(BorderLayout.NORTH, new JLabel("GoogleWave - elliptical segments")); - - scope = new AudioScope(synth); - scope.addProbe(googleWaveUnit.output); - scope.setTriggerMode(AudioScope.TriggerMode.NORMAL); - scope.getView().setShowControls(false); - scope.start(); - add(BorderLayout.CENTER, scope.getView()); - - JPanel southPanel = new JPanel(); - southPanel.setLayout(new GridLayout(0, 1)); - add(BorderLayout.SOUTH, southPanel); - - southPanel.add(PortControllerFactory.createExponentialPortSlider(googleWaveUnit.frequency)); - southPanel.add(PortControllerFactory.createExponentialPortSlider(googleWaveUnit.variance)); - southPanel.add(PortControllerFactory.createExponentialPortSlider(googleWaveUnit.amplitude)); - - validate(); - } - - @Override - public void start() { - synth = JSyn.createSynthesizer(); - synth.add(googleWaveUnit = new GoogleWaveOscillator()); - googleWaveUnit.amplitude.setup(0.02, 0.5, 1.0); - googleWaveUnit.variance.setup(0.0, 0.0, 1.0); - googleWaveUnit.frequency.setup(40.0, 200.0, 1000.0); - - // Add an output so we can hear it. - synth.add(lineOut = new LineOut()); - - googleWaveUnit.output.connect(0, lineOut.input, 0); - googleWaveUnit.output.connect(0, lineOut.input, 1); - - setupGUI(); - - // Start synthesizer using default stereo output at 44100 Hz. - synth.start(); - // Start lineOut so it can pull data from other units. - lineOut.start(); - - // We only need to start the LineOut. It will pull data from the - // oscillator. - lineOut.start(); - - } - - @Override - public void stop() { - scope.stop(); - synth.stop(); - } - -} diff --git a/tests/com/jsyn/examples/SeeOscillators.java b/tests/com/jsyn/examples/SeeOscillators.java deleted file mode 100644 index b8088c4..0000000 --- a/tests/com/jsyn/examples/SeeOscillators.java +++ /dev/null @@ -1,220 +0,0 @@ -/* - * Copyright 2010 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.examples; - -import java.awt.BorderLayout; -import java.awt.GridLayout; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.util.ArrayList; - -import javax.swing.ButtonGroup; -import javax.swing.JApplet; -import javax.swing.JLabel; -import javax.swing.JPanel; -import javax.swing.JRadioButton; - -import com.jsyn.JSyn; -import com.jsyn.Synthesizer; -import com.jsyn.scope.AudioScope; -import com.jsyn.scope.AudioScopeProbe; -import com.jsyn.swing.DoubleBoundedRangeSlider; -import com.jsyn.swing.JAppletFrame; -import com.jsyn.swing.PortControllerFactory; -import com.jsyn.unitgen.ImpulseOscillator; -import com.jsyn.unitgen.ImpulseOscillatorBL; -import com.jsyn.unitgen.LineOut; -import com.jsyn.unitgen.LinearRamp; -import com.jsyn.unitgen.MorphingOscillatorBL; -import com.jsyn.unitgen.Multiply; -import com.jsyn.unitgen.PulseOscillator; -import com.jsyn.unitgen.PulseOscillatorBL; -import com.jsyn.unitgen.RedNoise; -import com.jsyn.unitgen.SawtoothOscillator; -import com.jsyn.unitgen.SawtoothOscillatorBL; -import com.jsyn.unitgen.SawtoothOscillatorDPW; -import com.jsyn.unitgen.SineOscillator; -import com.jsyn.unitgen.SquareOscillator; -import com.jsyn.unitgen.SquareOscillatorBL; -import com.jsyn.unitgen.TriangleOscillator; -import com.jsyn.unitgen.UnitOscillator; - -/** - * Display each oscillator's waveform using the AudioScope. This is a re-implementation of the - * TJ_SeeOsc Applet from the old API. - * - * @author Phil Burk (C) 2010 Mobileer Inc - */ -public class SeeOscillators extends JApplet { - private static final long serialVersionUID = -8315903842197137926L; - private Synthesizer synth; - private ArrayList oscillators = new ArrayList(); - private LineOut lineOut; - private AudioScope scope; - private JPanel oscPanel; - private Multiply oscGain; - private ButtonGroup buttonGroup; - private LinearRamp freqRamp; - private LinearRamp widthRamp; - private LinearRamp shapeRamp; - private DoubleBoundedRangeSlider widthSlider; - private DoubleBoundedRangeSlider shapeSlider; - - /* Can be run as either an application or as an applet. */ - public static void main(String args[]) { - SeeOscillators applet = new SeeOscillators(); - JAppletFrame frame = new JAppletFrame("ShowWaves", applet); - frame.setSize(640, 500); - frame.setVisible(true); - frame.test(); - frame.validate(); - } - - private void setupGUI() { - setLayout(new BorderLayout()); - - add(BorderLayout.NORTH, new JLabel("Show Oscillators in an AudioScope")); - - scope = new AudioScope(synth); - AudioScopeProbe probe = scope.addProbe(oscGain.output); - probe.setAutoScaleEnabled(false); - probe.setVerticalScale(1.1); - scope.setTriggerMode(AudioScope.TriggerMode.NORMAL); - // scope.getModel().getTriggerModel().getLevelModel().setDoubleValue( 0.0001 ); - // Turn off the gain and trigger control GUI. - scope.getView().setControlsVisible(false); - scope.start(); - add(BorderLayout.CENTER, scope.getView()); - - JPanel southPanel = new JPanel(); - southPanel.setLayout(new GridLayout(0, 1)); - add(BorderLayout.SOUTH, southPanel); - - oscPanel = new JPanel(); - oscPanel.setLayout(new GridLayout(2, 5)); - southPanel.add(oscPanel); - - southPanel.add(PortControllerFactory.createExponentialPortSlider(freqRamp.input)); - southPanel.add(PortControllerFactory.createExponentialPortSlider(oscGain.inputB)); - southPanel.add(widthSlider = PortControllerFactory.createPortSlider(widthRamp.input)); - widthSlider.setEnabled(false); - southPanel.add(shapeSlider = PortControllerFactory.createPortSlider(shapeRamp.input)); - shapeSlider.setEnabled(false); - - oscPanel.validate(); - validate(); - } - - @Override - public void start() { - synth = JSyn.createSynthesizer(); - - // Use a multiplier for gain control and so we can hook up to the scope - // from a single unit. - synth.add(oscGain = new Multiply()); - oscGain.inputB.setup(0.02, 0.5, 1.0); - oscGain.inputB.setName("Amplitude"); - - synth.add(freqRamp = new LinearRamp()); - freqRamp.input.setup(50.0, 300.0, 20000.0); - freqRamp.input.setName("Frequency"); - freqRamp.time.set(0.1); - - synth.add(widthRamp = new LinearRamp()); - widthRamp.input.setup(-1.0, 0.0, 1.0); - widthRamp.input.setName("Width"); - widthRamp.time.set(0.1); - - synth.add(shapeRamp = new LinearRamp()); - shapeRamp.input.setup(-1.0, 0.0, 1.0); - shapeRamp.input.setName("Shape"); - shapeRamp.time.set(0.1); - - // Add an output so we can hear the oscillators. - synth.add(lineOut = new LineOut()); - - oscGain.output.connect(0, lineOut.input, 0); - oscGain.output.connect(0, lineOut.input, 1); - - setupGUI(); - - buttonGroup = new ButtonGroup(); - - addOscillator(new SineOscillator(), "Sine"); - addOscillator(new TriangleOscillator(), "Triangle"); - addOscillator(new SawtoothOscillator(), "Sawtooth"); - addOscillator(new SawtoothOscillatorBL(), "SawBL"); - addOscillator(new SawtoothOscillatorDPW(), "SawDPW"); - addOscillator(new RedNoise(), "RedNoise"); - - addOscillator(new SquareOscillator(), "Square"); - addOscillator(new SquareOscillatorBL(), "SquareBL"); - addOscillator(new PulseOscillator(), "Pulse"); - addOscillator(new PulseOscillatorBL(), "PulseBL"); - addOscillator(new MorphingOscillatorBL(), "MorphBL"); - addOscillator(new ImpulseOscillator(), "Impulse"); - addOscillator(new ImpulseOscillatorBL(), "ImpulseBL"); - - // Start synthesizer using default stereo output at 44100 Hz. - synth.start(); - // Start lineOut so it can pull data from other units. - lineOut.start(); - - // We only need to start the LineOut. It will pull data from the - // oscillator. - lineOut.start(); - - } - - private void addOscillator(final UnitOscillator osc, String label) { - oscillators.add(osc); - synth.add(osc); - freqRamp.output.connect(osc.frequency); - if (osc instanceof PulseOscillatorBL) { - widthRamp.output.connect(((PulseOscillatorBL)osc).width); - } - if (osc instanceof PulseOscillator) { - widthRamp.output.connect(((PulseOscillator)osc).width); - } - if (osc instanceof MorphingOscillatorBL) { - shapeRamp.output.connect(((MorphingOscillatorBL)osc).shape); - } - osc.amplitude.set(1.0); - JRadioButton checkBox = new JRadioButton(label); - buttonGroup.add(checkBox); - checkBox.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent arg0) { - // Disconnect other oscillators. - oscGain.inputA.disconnectAll(0); - // Connect this one. - osc.output.connect(oscGain.inputA); - widthSlider.setEnabled(osc instanceof PulseOscillator - || osc instanceof PulseOscillatorBL); - shapeSlider.setEnabled(osc instanceof MorphingOscillatorBL); - } - }); - oscPanel.add(checkBox); - } - - @Override - public void stop() { - scope.stop(); - synth.stop(); - } - -} diff --git a/tests/com/jsyn/examples/ShowWaves.java b/tests/com/jsyn/examples/ShowWaves.java deleted file mode 100644 index b1dd215..0000000 --- a/tests/com/jsyn/examples/ShowWaves.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright 2010 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.examples; - -import java.awt.BorderLayout; -import java.util.ArrayList; - -import javax.swing.JApplet; -import javax.swing.JLabel; - -import com.jsyn.JSyn; -import com.jsyn.Synthesizer; -import com.jsyn.scope.AudioScope; -import com.jsyn.swing.JAppletFrame; -import com.jsyn.unitgen.Add; -import com.jsyn.unitgen.LineOut; -import com.jsyn.unitgen.SawtoothOscillatorBL; -import com.jsyn.unitgen.SineOscillator; -import com.jsyn.unitgen.TriangleOscillator; -import com.jsyn.unitgen.UnitOscillator; - -/** - * Display waveforms using the AudioScope. The frequency of the oscillators is modulated by an LFO. - * - * @author Phil Burk (C) 2010 Mobileer Inc - */ -public class ShowWaves extends JApplet { - private static final long serialVersionUID = -8315903842197137926L; - private Synthesizer synth; - private UnitOscillator lfo; - private Add adder; - private ArrayList oscillators = new ArrayList(); - private LineOut lineOut; - private AudioScope scope; - - /* Can be run as either an application or as an applet. */ - public static void main(String args[]) { - ShowWaves applet = new ShowWaves(); - JAppletFrame frame = new JAppletFrame("ShowWaves", applet); - frame.setSize(640, 300); - frame.setVisible(true); - frame.test(); - } - - private void setupGUI() { - setLayout(new BorderLayout()); - - add(BorderLayout.NORTH, new JLabel("ShowWaves in an AudioScope Mod001")); - - scope = new AudioScope(synth); - for (UnitOscillator osc : oscillators) { - scope.addProbe(osc.output); - } - scope.setTriggerMode(AudioScope.TriggerMode.NORMAL); - scope.start(); - - // Turn on the gain and trigger control GUI. - scope.getView().setControlsVisible(true); - add(BorderLayout.CENTER, scope.getView()); - validate(); - } - - @Override - public void start() { - synth = JSyn.createSynthesizer(); - - // Add an LFO. - synth.add(lfo = new SineOscillator()); - synth.add(adder = new Add()); - - // Add an output so we can hear the oscillators. - synth.add(lineOut = new LineOut()); - - lfo.frequency.set(0.1); - lfo.amplitude.set(200.0); - adder.inputB.set(400.0); - lfo.output.connect(adder.inputA); - - oscillators.add(new SawtoothOscillatorBL()); - oscillators.add(new SineOscillator()); - oscillators.add(new TriangleOscillator()); - for (UnitOscillator osc : oscillators) { - synth.add(osc); - adder.output.connect(osc.frequency); - osc.output.connect(0, lineOut.input, 0); - osc.amplitude.set(0.2); - } - - // Start synthesizer using default stereo output at 44100 Hz. - synth.start(); - // Start lineOut so it can pull data from other units. - lineOut.start(); - setupGUI(); - - // We only need to start the LineOut. It will pull data from the - // oscillator. - lineOut.start(); - - } - - @Override - public void stop() { - scope.stop(); - synth.stop(); - } - -} diff --git a/tests/com/jsyn/examples/SwarmOfOscillators.java b/tests/com/jsyn/examples/SwarmOfOscillators.java deleted file mode 100644 index 9f7c19c..0000000 --- a/tests/com/jsyn/examples/SwarmOfOscillators.java +++ /dev/null @@ -1,146 +0,0 @@ -/* - * 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.examples; - -import com.jsyn.JSyn; -import com.jsyn.Synthesizer; -import com.jsyn.ports.UnitOutputPort; -import com.jsyn.unitgen.Add; -import com.jsyn.unitgen.AsymptoticRamp; -import com.jsyn.unitgen.Circuit; -import com.jsyn.unitgen.LineOut; -import com.jsyn.unitgen.Pan; -import com.jsyn.unitgen.SawtoothOscillatorDPW; -import com.jsyn.unitgen.SineOscillator; -import com.jsyn.unitgen.UnitOscillator; -import com.jsyn.unitgen.UnitSource; - -/** - * Make a bunch of oscillators that swarm around a moving frequency. - * - * @author Phil Burk (C) 2009 Mobileer Inc - */ -public class SwarmOfOscillators { - private Synthesizer synth; - Follower[] followers; - SineOscillator lfo; - LineOut lineOut; - private Add tiePoint; - private static final int NUM_FOLLOWERS = 30; - - class Follower extends Circuit implements UnitSource { - UnitOscillator osc; - AsymptoticRamp lag; - Pan panner; - - Follower() { - // Add a tone generator. - add(osc = new SawtoothOscillatorDPW()); - osc.amplitude.set(0.03); - - // Use a lag to smoothly change frequency. - add(lag = new AsymptoticRamp()); - double hlife = 0.01 + (Math.random() * 0.9); - lag.halfLife.set(hlife); - - // Set left/right pan randomly between -1.0 and +1.0. - add(panner = new Pan()); - panner.pan.set((Math.random() * 2.0) - 1.0); - - // Track the frequency coming through the tiePoint. - tiePoint.output.connect(lag.input); - // Add the LFO offset. - lfo.output.connect(lag.input); - - lag.output.connect(osc.frequency); - - // Connect the oscillator to the left and right audio output. - osc.output.connect(panner.input); - } - - @Override - public UnitOutputPort getOutput() { - return panner.output; - } - } - - private void test() { - synth = JSyn.createSynthesizer(); - - // Add an output mixer. - synth.add(lineOut = new LineOut()); - - // Add a unit just to distribute the control frequency. - synth.add(tiePoint = new Add()); - synth.add(lfo = new SineOscillator()); - lfo.amplitude.set(40.0); - lfo.frequency.set(2.3); - - followers = new Follower[NUM_FOLLOWERS]; - for (int i = 0; i < followers.length; i++) { - Follower follower = new Follower(); - synth.add(follower); - - // Every follower can connect directly to the lineOut because all input ports are - // mixers. - follower.getOutput().connect(0, lineOut.input, 0); - follower.getOutput().connect(1, lineOut.input, 1); - - followers[i] = follower; - } - - // Start synthesizer using default stereo output at 44100 Hz. - synth.start(); - // We only need to start the LineOut. It will pull data from the - // oscillator. - lineOut.start(); - - // Get synthesizer time in seconds. - double timeNow = synth.getCurrentTime(); - - // Advance to a near future time so we have a clean start. - double duration = 0.9; - double time = timeNow + duration; - double freq = 400.0; // hertz - tiePoint.inputA.set(freq, time); - - // Randomly change the target frequency for the followers. - try { - for (int i = 0; i < 20; i++) { - // Schedule this to happen a bit later. - time += duration; - freq = 200.0 + (Math.random() * 500.0); - tiePoint.inputA.set(freq, time); - - // Sleep while the sound is being generated in the background - // thread. - synth.sleepUntil(time - 0.2); - } - } catch (InterruptedException e) { - e.printStackTrace(); - } - - System.out.format("CPU usage = %4.2f%c\n", synth.getUsage() * 100, '%'); - - // Stop everything. - synth.stop(); - } - - public static void main(String[] args) { - new SwarmOfOscillators().test(); - } -} diff --git a/tests/com/jsyn/examples/UseMidiKeyboard.java b/tests/com/jsyn/examples/UseMidiKeyboard.java deleted file mode 100644 index 0efa039..0000000 --- a/tests/com/jsyn/examples/UseMidiKeyboard.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright 2010 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.examples; - -import java.io.IOException; - -import javax.sound.midi.MidiDevice; -import javax.sound.midi.MidiMessage; -import javax.sound.midi.MidiUnavailableException; -import javax.sound.midi.Receiver; - -import com.jsyn.JSyn; -import com.jsyn.Synthesizer; -import com.jsyn.devices.javasound.MidiDeviceTools; -import com.jsyn.instruments.DualOscillatorSynthVoice; -import com.jsyn.midi.MidiSynthesizer; -import com.jsyn.unitgen.LineOut; -import com.jsyn.util.MultiChannelSynthesizer; -import com.jsyn.util.VoiceDescription; - -/** - * Connect a USB MIDI Keyboard to the internal MIDI Synthesizer using JavaSound. - * - * @author Phil Burk (C) 2010 Mobileer Inc - */ -public class UseMidiKeyboard { - private static final int NUM_CHANNELS = 16; - private static final int VOICES_PER_CHANNEL = 3; - - private Synthesizer synth; - private LineOut lineOut; - private MidiSynthesizer midiSynthesizer; - private VoiceDescription voiceDescription; - private MultiChannelSynthesizer multiSynth; - - public static void main(String[] args) { - UseMidiKeyboard app = new UseMidiKeyboard(); - try { - app.test(); - } catch (MidiUnavailableException e) { - e.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - - // Write a Receiver to get the messages from a Transmitter. - class CustomReceiver implements Receiver { - @Override - public void close() { - System.out.print("Closed."); - } - - @Override - public void send(MidiMessage message, long timeStamp) { - byte[] bytes = message.getMessage(); - midiSynthesizer.onReceive(bytes, 0, bytes.length); - } - } - - public int test() throws MidiUnavailableException, IOException, InterruptedException { - setupSynth(); - - int result = 2; - MidiDevice keyboard = MidiDeviceTools.findKeyboard(); - Receiver receiver = new CustomReceiver(); - // Just use default synthesizer. - if (keyboard != null) { - // If you forget to open them you will hear no sound. - keyboard.open(); - // Put the receiver in the transmitter. - // This gives fairly low latency playing. - keyboard.getTransmitter().setReceiver(receiver); - System.out.println("Play MIDI keyboard: " + keyboard.getDeviceInfo().getDescription()); - result = 0; - } else { - System.out.println("Could not find a keyboard."); - } - return result; - } - - - private void setupSynth() { - synth = JSyn.createSynthesizer(); - - voiceDescription = DualOscillatorSynthVoice.getVoiceDescription(); -// voiceDescription = SubtractiveSynthVoice.getVoiceDescription(); - - multiSynth = new MultiChannelSynthesizer(); - final int startChannel = 0; - multiSynth.setup(synth, startChannel, NUM_CHANNELS, VOICES_PER_CHANNEL, voiceDescription); - midiSynthesizer = new MidiSynthesizer(multiSynth); - - // Create a LineOut for the entire synthesizer. - synth.add(lineOut = new LineOut()); - multiSynth.getOutput().connect(0,lineOut.input, 0); - multiSynth.getOutput().connect(1,lineOut.input, 1); - - // Start synthesizer using default stereo output at 44100 Hz. - synth.start(); - lineOut.start(); - - } - -} diff --git a/tests/com/jsyn/examples/WindCircuit.java b/tests/com/jsyn/examples/WindCircuit.java deleted file mode 100644 index 1e3623e..0000000 --- a/tests/com/jsyn/examples/WindCircuit.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright 1997 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.examples; - -import com.jsyn.ports.UnitInputPort; -import com.jsyn.ports.UnitOutputPort; -import com.jsyn.unitgen.Circuit; -import com.jsyn.unitgen.FilterStateVariable; -import com.jsyn.unitgen.MultiplyAdd; -import com.jsyn.unitgen.RedNoise; -import com.jsyn.unitgen.UnitSource; -import com.jsyn.unitgen.WhiteNoise; - -/** - * Wind Sound Create a wind-like sound by feeding white noise "shshshshsh" through a randomly - * varying state filter to make a "whooowhoosh" sound. The cuttoff frequency of the low pass filter - * is controlled by a RedNoise unit which creates a slowly varying random control signal. - * - * @author (C) 1997 Phil Burk, SoftSynth.com - */ - -public class WindCircuit extends Circuit implements UnitSource { - /* Declare units that will be part of the circuit. */ - WhiteNoise myNoise; - FilterStateVariable myFilter; - RedNoise myLFO; - MultiplyAdd myScalar; - - /* Declare ports. */ - public UnitInputPort noiseAmp; - public UnitInputPort modRate; - public UnitInputPort modDepth; - public UnitInputPort cutoff; - public UnitInputPort resonance; - public UnitInputPort amplitude; - public UnitOutputPort output; - - public WindCircuit() { - /* - * Create various unit generators and add them to circuit. - */ - add(myNoise = new WhiteNoise()); - add(myFilter = new FilterStateVariable()); - add(myLFO = new RedNoise()); - add(myScalar = new MultiplyAdd()); - - /* Make ports on internal units appear as ports on circuit. */ - /* Optionally give some circuit ports more meaningful names. */ - addPort(noiseAmp = myNoise.amplitude, "NoiseAmp"); - addPort(modRate = myLFO.frequency, "ModRate"); - addPort(modDepth = myScalar.inputB, "ModDepth"); - addPort(cutoff = myScalar.inputC, "Cutoff"); - addPort(resonance = myFilter.resonance); - addPort(amplitude = myFilter.amplitude); - addPort(output = myFilter.output); - - /* Connect SynthUnits to make control signal path. */ - myLFO.output.connect(myScalar.inputA); - myScalar.output.connect(myFilter.frequency); - /* Connect SynthUnits to make audio signal path. */ - myNoise.output.connect(myFilter.input); - - /* Set ports to useful values and ranges. */ - noiseAmp.setup(0.0, 0.3, 0.4); - modRate.setup(0.0, 1.0, 10.0); - modDepth.setup(0.0, 300.0, 1000.0); - cutoff.setup(0.0, 600.0, 1000.0); - resonance.setup(0.0, 0.066, 0.2); - amplitude.setup(0.0, 0.9, 0.999); - } - - @Override - public UnitOutputPort getOutput() { - return output; - } -} diff --git a/tests/com/jsyn/midi/TestMidiLoop.java b/tests/com/jsyn/midi/TestMidiLoop.java deleted file mode 100644 index 5837696..0000000 --- a/tests/com/jsyn/midi/TestMidiLoop.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright 2010 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.midi; - -import java.io.IOException; - -import javax.sound.midi.MidiDevice; -import javax.sound.midi.MidiMessage; -import javax.sound.midi.MidiUnavailableException; -import javax.sound.midi.Receiver; - -import com.jsyn.JSyn; -import com.jsyn.Synthesizer; -import com.jsyn.devices.javasound.MidiDeviceTools; -import com.jsyn.instruments.DualOscillatorSynthVoice; -import com.jsyn.midi.MidiSynthesizer; -import com.jsyn.unitgen.LineOut; -import com.jsyn.util.MultiChannelSynthesizer; -import com.jsyn.util.VoiceDescription; - -/** - * Connect a USB MIDI Keyboard to the internal MIDI Synthesizer using JavaSound. - * - * @author Phil Burk (C) 2010 Mobileer Inc - */ -public class TestMidiLoop { - - public static void main(String[] args) { - TestMidiLoop app = new TestMidiLoop(); - int result = 0; - try { - for (int i = 0; i < 3 && result == 0; i++) { - result = app.test(); - } - } catch (MidiUnavailableException e) { - e.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); - } catch (InterruptedException e) { - e.printStackTrace(); - } - System.out.print("Test finished."); - System.exit((result == 0) ? 0 : 1); - } - - // Write a Receiver to get the messages from a Transmitter. - class CustomReceiver implements Receiver { - @Override - public void close() { - System.out.print("Receiver.close() was called."); - } - - @Override - public void send(MidiMessage message, long timeStamp) { - byte[] bytes = message.getMessage(); - System.out.println("Got " + bytes.length + " bytes."); - } - } - - public int test() throws MidiUnavailableException, IOException, InterruptedException { - - int result = -1; - MidiDevice keyboard = MidiDeviceTools.findKeyboard(); - Receiver receiver = new CustomReceiver(); - // Just use default synthesizer. - if (keyboard != null) { - // If you forget to open them you will hear no sound. - keyboard.open(); - // Put the receiver in the transmitter. - // This gives fairly low latency playing. - keyboard.getTransmitter().setReceiver(receiver); - System.out.println("Play MIDI keyboard: " + keyboard.getDeviceInfo().getDescription()); - result = 0; - Thread.sleep(4000); - System.out.println("Close the keyboard. It may not work after this according to the docs!"); - keyboard.close(); - } else { - System.out.println("Could not find a keyboard."); - } - return result; - } - - -} diff --git a/tests/com/jsyn/ports/TestQueuedDataPort.java b/tests/com/jsyn/ports/TestQueuedDataPort.java deleted file mode 100644 index e229038..0000000 --- a/tests/com/jsyn/ports/TestQueuedDataPort.java +++ /dev/null @@ -1,525 +0,0 @@ -/* - * 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 junit.framework.TestCase; - -import com.jsyn.JSyn; -import com.jsyn.Synthesizer; -import com.jsyn.data.FloatSample; -import com.jsyn.data.SequentialData; -import com.jsyn.data.ShortSample; -import com.jsyn.unitgen.FixedRateMonoReader; - -/** - * Test sample and envelope queuing and looping. - * - * @author Phil Burk, (C) 2009 Mobileer Inc - */ -public class TestQueuedDataPort extends TestCase { - Synthesizer synth; - float[] floatData = { - 0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f - }; - FloatSample floatSample; - FixedRateMonoReader reader; - - @Override - protected void setUp() throws Exception { - super.setUp(); - synth = JSyn.createSynthesizer(); - synth.setRealTime(false); - synth.start(); - } - - @Override - protected void tearDown() throws Exception { - synth.stop(); - super.tearDown(); - } - - private void queueDirect(UnitDataQueuePort port, SequentialData data, int startFrame, - int numFrames) { - queueDirect(port, data, startFrame, numFrames, 0); - } - - private void queueDirect(UnitDataQueuePort port, SequentialData data, int startFrame, - int numFrames, int numLoops) { - QueueDataCommand command = port.createQueueDataCommand(data, startFrame, numFrames); - command.setNumLoops(numLoops); - port.addQueuedBlock(command); - } - - public void testQueueSingleShort() { - short[] data = { - 234, -9876, 4567 - }; - ShortSample sample = new ShortSample(data.length, 1); - sample.write(data); - - UnitDataQueuePort dataQueue = new UnitDataQueuePort("test"); - assertEquals("start empty", false, dataQueue.hasMore()); - - queueDirect(dataQueue, sample, 0, data.length); - checkQueuedData(data, dataQueue, 0, data.length); - - assertEquals("end empty", false, dataQueue.hasMore()); - } - - public void testQueueSingleFloat() { - float[] data = { - 0.4f, 1.9f, 22.7f - }; - FloatSample sample = new FloatSample(data.length, 1); - sample.write(data); - - UnitDataQueuePort dataQueue = new UnitDataQueuePort("test"); - assertEquals("start empty", false, dataQueue.hasMore()); - - queueDirect(dataQueue, sample, 0, data.length); - checkQueuedData(data, dataQueue, 0, data.length); - - assertEquals("end empty", false, dataQueue.hasMore()); - } - - public void testQueueOutOfBounds() { - float[] data = { - 0.4f, 1.9f, 22.7f - }; - FloatSample sample = new FloatSample(data.length, 1); - sample.write(data); - - UnitDataQueuePort dataQueue = new UnitDataQueuePort("test"); - boolean caught = false; - try { - queueDirect(dataQueue, sample, 0, sample.getNumFrames() + 1); // should cause an error! - } catch(IllegalArgumentException e) { - caught = true; - } - assertTrue("expect exception when we go past end of the array", caught); - - caught = false; - try { - queueDirect(dataQueue, sample, 1, sample.getNumFrames()); // should cause an error! - } catch(IllegalArgumentException e) { - caught = true; - } - assertTrue("expect exception when we go past end of the array", caught); - - caught = false; - try { - queueDirect(dataQueue, sample, -1, sample.getNumFrames()); // should cause an error! - } catch(IllegalArgumentException e) { - caught = true; - } - assertTrue("expect exception when we start before beginning of the array", caught); - } - - public void testQueueMultiple() { - short[] data = { - 234, 17777, -9876, 4567, -14287 - }; - ShortSample sample = new ShortSample(data.length, 1); - sample.write(data); - - UnitDataQueuePort dataQueue = new UnitDataQueuePort("test"); - assertEquals("start empty", false, dataQueue.hasMore()); - - queueDirect(dataQueue, sample, 1, 3); - queueDirect(dataQueue, sample, 0, 5); - queueDirect(dataQueue, sample, 2, 2); - - checkQueuedData(data, dataQueue, 1, 3); - checkQueuedData(data, dataQueue, 0, 5); - checkQueuedData(data, dataQueue, 2, 2); - - assertEquals("end empty", false, dataQueue.hasMore()); - } - - public void testQueueNoLoops() throws InterruptedException { - System.out.println("testQueueNoLoops() ================"); - UnitDataQueuePort dataQueue = setupFloatSample(); - - dataQueue.queueOn(floatSample, synth.createTimeStamp()); - // Advance synth so that the queue command propagates to the engine. - synth.sleepUntil(synth.getCurrentTime() + 0.01); - - // play entire sample - checkQueuedData(floatData, dataQueue, 0, floatData.length); - - assertEquals("end empty", false, dataQueue.hasMore()); - } - - public void testQueueLoopForever() throws InterruptedException { - System.out.println("testQueueLoopForever() ================"); - - UnitDataQueuePort dataQueue = setupFloatSample(); - - dataQueue.queue(floatSample, 0, 3); - dataQueue.queueLoop(floatSample, 3, 4); - - // Advance synth so that the queue commands propagate to the engine. - synth.sleepUntil(synth.getCurrentTime() + 0.01); - - checkQueuedData(floatData, dataQueue, 0, 3); - checkQueuedData(floatData, dataQueue, 3, 4); - checkQueuedData(floatData, dataQueue, 3, 4); - checkQueuedData(floatData, dataQueue, 3, 4); - checkQueuedData(floatData, dataQueue, 3, 1); - - // queue final release - dataQueue.queue(floatSample, 3, 5); - synth.sleepUntil(synth.getCurrentTime() + 0.01); - // current loop will finish - checkQueuedData(floatData, dataQueue, 4, 3); - // release portion will play - checkQueuedData(floatData, dataQueue, 3, 5); - - assertEquals("end empty", false, dataQueue.hasMore()); - } - - public void testQueueLoopAtLeastOnce() throws InterruptedException { - System.out.println("testQueueLoopAtLeastOnce() ================"); - - UnitDataQueuePort dataQueue = setupFloatSample(); - - dataQueue.queue(floatSample, 0, 3); - dataQueue.queueLoop(floatSample, 3, 2); // this should play at least once - dataQueue.queue(floatSample, 5, 2); - - // Advance synth so that the queue commands propagate to the engine. - synth.sleepUntil(synth.getCurrentTime() + 0.01); - - checkQueuedData(floatData, dataQueue, 0, 3); - checkQueuedData(floatData, dataQueue, 3, 2); - checkQueuedData(floatData, dataQueue, 5, 2); - - assertEquals("end empty", false, dataQueue.hasMore()); - } - - public void testQueueNumLoops() throws InterruptedException { - System.out.println("testQueueNumLoops() ================"); - UnitDataQueuePort dataQueue = setupFloatSample(); - - dataQueue.queue(floatSample, 0, 2); - - int numLoopsA = 5; - dataQueue.queueLoop(floatSample, 2, 3, numLoopsA); - - dataQueue.queue(floatSample, 4, 2); - - int numLoopsB = 3; - dataQueue.queueLoop(floatSample, 3, 4, numLoopsB); - - dataQueue.queue(floatSample, 5, 2); - - // Advance synth so that the queue commands propagate to the engine. - synth.sleepUntil(synth.getCurrentTime() + 0.01); - - checkQueuedData(floatData, dataQueue, 0, 2); - for (int i = 0; i < (numLoopsA + 1); i++) { - System.out.println("loop A #" + i); - checkQueuedData(floatData, dataQueue, 2, 3); - } - checkQueuedData(floatData, dataQueue, 4, 2); - for (int i = 0; i < (numLoopsB + 1); i++) { - System.out.println("loop B #" + i); - checkQueuedData(floatData, dataQueue, 3, 4); - } - - checkQueuedData(floatData, dataQueue, 5, 2); - - assertEquals("end empty", false, dataQueue.hasMore()); - } - - private UnitDataQueuePort setupFloatSample() { - floatSample = new FloatSample(floatData.length, 1); - floatSample.write(floatData); - - synth.add(reader = new FixedRateMonoReader()); - UnitDataQueuePort dataQueue = reader.dataQueue; - assertEquals("start empty", false, dataQueue.hasMore()); - return dataQueue; - } - - public void testQueueSustainLoop() throws InterruptedException { - System.out.println("testQueueSustainLoop() ================"); - - UnitDataQueuePort dataQueue = setupFloatSample(); - - // set up sustain loops =========================== - floatSample.setSustainBegin(2); - floatSample.setSustainEnd(4); - floatSample.setReleaseBegin(-1); - floatSample.setReleaseEnd(-1); - - dataQueue.queueOn(floatSample, synth.createTimeStamp()); - // Advance synth so that the queue command propagates to the engine. - synth.sleepUntil(synth.getCurrentTime() + 0.01); - - checkQueuedData(floatData, dataQueue, 0, 2); - checkQueuedData(floatData, dataQueue, 2, 2); - checkQueuedData(floatData, dataQueue, 2, 2); - checkQueuedData(floatData, dataQueue, 2, 1); // looping - - dataQueue.queueOff(floatSample, true); // queue off in middle of loop - synth.sleepUntil(synth.getCurrentTime() + 0.01); - - checkQueuedData(floatData, dataQueue, 3, 5); // release - assertEquals("end empty", false, dataQueue.hasMore()); - } - - public void testQueueReleaseLoop() throws InterruptedException { - System.out.println("testQueueReleaseLoop() ================"); - UnitDataQueuePort dataQueue = setupFloatSample(); - - // set up sustain loops =========================== - floatSample.setSustainBegin(-1); - floatSample.setSustainEnd(-1); - floatSample.setReleaseBegin(4); - floatSample.setReleaseEnd(6); - - dataQueue.queueOn(floatSample, synth.createTimeStamp()); - // Advance synth so that the queue command propagates to the engine. - synth.sleepUntil(synth.getCurrentTime() + 0.01); - - checkQueuedData(floatData, dataQueue, 0, 4); - checkQueuedData(floatData, dataQueue, 4, 2); - checkQueuedData(floatData, dataQueue, 4, 2); - checkQueuedData(floatData, dataQueue, 4, 2); // looping in release cuz no - // sustain loop - - dataQueue.queueOff(floatSample, true); // queue off in middle of loop - synth.sleepUntil(synth.getCurrentTime() + 0.01); - - checkQueuedData(floatData, dataQueue, 4, 2); - checkQueuedData(floatData, dataQueue, 4, 2); // still looping - assertEquals("end full", true, dataQueue.hasMore()); - } - - public void testQueueSustainReleaseLoops() throws InterruptedException { - System.out.println("testQueueSustainReleaseLoops() ================"); - UnitDataQueuePort dataQueue = setupFloatSample(); - - // set up sustain loops =========================== - floatSample.setSustainBegin(2); - floatSample.setSustainEnd(4); - floatSample.setReleaseBegin(5); - floatSample.setReleaseEnd(7); - - dataQueue.queueOn(floatSample, synth.createTimeStamp()); - // Advance synth so that the queue command propagates to the engine. - synth.sleepUntil(synth.getCurrentTime() + 0.01); - - checkQueuedData(floatData, dataQueue, 0, 4); - checkQueuedData(floatData, dataQueue, 2, 2); - checkQueuedData(floatData, dataQueue, 2, 1); // middle of sustain loop - - dataQueue.queueOff(floatSample, true); // queue off in middle of loop - synth.sleepUntil(synth.getCurrentTime() + 0.01); - - checkQueuedData(floatData, dataQueue, 3, 2); - checkQueuedData(floatData, dataQueue, 5, 2); // release loop - checkQueuedData(floatData, dataQueue, 5, 2); // release loop - assertEquals("end full", true, dataQueue.hasMore()); - } - - private void checkQueuedData(short[] data, UnitDataQueuePort dataQueue, int offset, - int numFrames) { - for (int i = 0; i < numFrames; i++) { - assertEquals("got data", true, dataQueue.hasMore()); - double value = dataQueue.readNextMonoDouble(synth.getFramePeriod()); - assertEquals("data matches", data[i + offset] / 32768.0, value, 0.0001); - } - } - - private void checkQueuedData(float[] data, UnitDataQueuePort dataQueue, int offset, - int numFrames) { - for (int i = 0; i < numFrames; i++) { - assertEquals("got data", true, dataQueue.hasMore()); - double value = dataQueue.readNextMonoDouble(synth.getFramePeriod()); - assertEquals("data matches", data[i + offset], value, 0.0001); - } - } - - class TestQueueCallback implements UnitDataQueueCallback { - boolean gotStarted = false; - boolean gotLooped = false; - boolean gotFinished = false; - QueueDataEvent lastEvent; - - @Override - public void started(QueueDataEvent event) { - System.out.println("Callback started."); - gotStarted = true; - lastEvent = event; - } - - @Override - public void looped(QueueDataEvent event) { - System.out.println("Callback looped."); - gotLooped = true; - lastEvent = event; - } - - @Override - public void finished(QueueDataEvent event) { - System.out.println("Callback finished."); - gotFinished = true; - lastEvent = event; - } - } - - public void testQueueCallback() { - float[] data = { - 0.2f, -8.9f, 2.7f - }; - FloatSample sample = new FloatSample(data.length, 1); - sample.write(data); - - UnitDataQueuePort dataQueue = new UnitDataQueuePort("test"); - assertEquals("start empty", false, dataQueue.hasMore()); - - // Create an object to be called when the queued data is done. - TestQueueCallback callback = new TestQueueCallback(); - - QueueDataCommand command = dataQueue.createQueueDataCommand(sample, 0, data.length); - command.setCallback(callback); - command.setNumLoops(2); - dataQueue.addQueuedBlock(command); - - // Check to see if flags get set true by callback. - dataQueue.firePendingCallbacks(); - assertEquals("not started yet", false, callback.gotStarted); - assertEquals("not looped yet", false, callback.gotLooped); - assertEquals("not finished yet", false, callback.gotFinished); - - checkQueuedData(data, dataQueue, 0, 1); - dataQueue.firePendingCallbacks(); - assertEquals("should be started now", true, callback.gotStarted); - assertEquals("not looped yet", false, callback.gotLooped); - assertEquals("not finished yet", false, callback.gotFinished); - assertEquals("check source of event", dataQueue, callback.lastEvent.getSource()); - assertEquals("check sample", sample, callback.lastEvent.getSequentialData()); - assertEquals("check loopCount", 2, callback.lastEvent.getLoopsLeft()); - - checkQueuedData(data, dataQueue, 1, data.length - 1); - dataQueue.firePendingCallbacks(); - assertEquals("should be looped now", true, callback.gotLooped); - assertEquals("check loopCount", 1, callback.lastEvent.getLoopsLeft()); - assertEquals("not finished yet", false, callback.gotFinished); - - checkQueuedData(data, dataQueue, 0, data.length); - dataQueue.firePendingCallbacks(); - assertEquals("check loopCount", 0, callback.lastEvent.getLoopsLeft()); - - checkQueuedData(data, dataQueue, 0, data.length); - dataQueue.firePendingCallbacks(); - assertEquals("should be finished now", true, callback.gotFinished); - - assertEquals("end empty", false, dataQueue.hasMore()); - } - - public void testImmediate() { - float[] data = { - 0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f, 10.0f, 11.0f - }; - FloatSample sample = new FloatSample(data.length, 1); - sample.write(data); - - UnitDataQueuePort dataQueue = new UnitDataQueuePort("test"); - dataQueue.queue(sample); - - // Only play some of the data then interrupt it with an immediate block. - checkQueuedData(data, dataQueue, 0, 3); - - QueueDataCommand command = dataQueue.createQueueDataCommand(sample, 7, 3); - command.setImmediate(true); - command.run(); // execute "immediate" operation and add to block list - - // Should already be in new data. - checkQueuedData(data, dataQueue, 7, 3); - } - - public void testCrossFade() { - float[] data1 = { - 0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f - }; - float[] data2 = { - 20.0f, 19.0f, 18.0f, 17.0f, 16.0f, 15.0f, 14.0f, 13.0f, 12.0f, 11.0f - }; - FloatSample sample1 = new FloatSample(data1); - FloatSample sample2 = new FloatSample(data2); - - UnitDataQueuePort dataQueue = new UnitDataQueuePort("test"); - dataQueue.queue(sample1, 0, 4); - - QueueDataCommand command = dataQueue.createQueueDataCommand(sample2, 1, 8); - command.setCrossFadeIn(3); - command.run(); // execute "immediate" operation and add to block list - - // Only play some of the data then crossfade to another sample. - checkQueuedData(data1, dataQueue, 0, 4); - - for (int i = 0; i < 3; i++) { - double factor = i / 3.0; - double value = ((1.0 - factor) * data1[i + 4]) + (factor * data2[i + 1]); - System.out.println("i = " + i + ", factor = " + factor + ", value = " + value); - - double actual = dataQueue.readNextMonoDouble(synth.getFramePeriod()); - assertEquals("crossfade " + i, value, actual, 0.00001); - } - - // Should already be in new data. - checkQueuedData(data2, dataQueue, 4, 5); - } - - public void testImmediateCrossFade() { - float[] data1 = { - 0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f - }; - float[] data2 = { - 20.0f, 19.0f, 18.0f, 17.0f, 16.0f, 15.0f, 14.0f, 13.0f, 12.0f, 11.0f - }; - FloatSample sample1 = new FloatSample(data1); - FloatSample sample2 = new FloatSample(data2); - - UnitDataQueuePort dataQueue = new UnitDataQueuePort("test"); - dataQueue.queue(sample1, 0, 4); - - // Only play some of the data then crossfade to another sample. - int beforeInterrupt = 2; - checkQueuedData(data1, dataQueue, 0, beforeInterrupt); - - QueueDataCommand command = dataQueue.createQueueDataCommand(sample2, 1, 8); - command.setImmediate(true); - command.setCrossFadeIn(3); - command.run(); // execute "immediate" operation and add to block list - - for (int i = 0; i < 3; i++) { - double factor = i / 3.0; - double value = ((1.0 - factor) * data1[i + beforeInterrupt]) + (factor * data2[i + 1]); - System.out.println("i = " + i + ", factor = " + factor + ", value = " + value); - - double actual = dataQueue.readNextMonoDouble(synth.getFramePeriod()); - assertEquals("crossfade " + i, value, actual, 0.00001); - } - - // Should already be in new data. - checkQueuedData(data2, dataQueue, 4, 5); - } -} diff --git a/tests/com/jsyn/ports/TestSequentialData.java b/tests/com/jsyn/ports/TestSequentialData.java deleted file mode 100644 index 1328c78..0000000 --- a/tests/com/jsyn/ports/TestSequentialData.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * 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 junit.framework.TestCase; - -import com.jsyn.data.FloatSample; - -public class TestSequentialData extends TestCase { - - float[] data1 = { - 0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f - }; - FloatSample sample1; - float[] data2 = { - 20.0f, 19.0f, 18.0f, 17.0f, 16.0f, 15.0f, 14.0f, 13.0f, 12.0f, 11.0f - }; - FloatSample sample2; - - public void testCrossfade() { - sample1 = new FloatSample(data1); - sample2 = new FloatSample(data2); - SequentialDataCrossfade xfade = new SequentialDataCrossfade(); - xfade.setup(sample1, 4, 3, sample2, 1, 6); - - for (int i = 0; i < 3; i++) { - double factor = i / 3.0; - double value = ((1.0 - factor) * data1[i + 4]) + (factor * data2[i + 1]); - System.out.println("i = " + i + ", factor = " + factor + ", value = " + value); - assertEquals("crossfade " + i, value, xfade.readDouble(i), 0.00001); - } - for (int i = 3; i < 6; i++) { - assertEquals("crossfade " + i, sample2.readDouble(i + 1), xfade.readDouble(i), 0.00001); - } - } -} diff --git a/tests/com/jsyn/ports/TestSet.java b/tests/com/jsyn/ports/TestSet.java deleted file mode 100644 index 8d1f3ea..0000000 --- a/tests/com/jsyn/ports/TestSet.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * 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 junit.framework.TestCase; - -import com.jsyn.engine.SynthesisEngine; -import com.jsyn.unitgen.Minimum; - -public class TestSet extends TestCase { - - @Override - protected void setUp() throws Exception { - super.setUp(); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - } - - /** Internal value setting. */ - public void testSetValue() { - int numParts = 4; - UnitInputPort port = new UnitInputPort(numParts, "Tester"); - port.setValueInternal(0, 100.0); - port.setValueInternal(2, 120.0); - port.setValueInternal(1, 110.0); - port.setValueInternal(3, 130.0); - assertEquals("check port value", 100.0, port.getValue(0)); - assertEquals("check port value", 120.0, port.getValue(2)); - assertEquals("check port value", 110.0, port.getValue(1)); - assertEquals("check port value", 130.0, port.getValue(3)); - } - - public void testSet() throws InterruptedException { - SynthesisEngine synthesisEngine = new SynthesisEngine(); - synthesisEngine.setRealTime(false); - synthesisEngine.start(); - synthesisEngine.sleepUntil(0.01); - Minimum min; - synthesisEngine.add(min = new Minimum()); - - double x = 33.99; - double y = 8.31; - min.inputA.set(x); - min.inputB.set(y); - synthesisEngine.sleepFor(0.01); - assertEquals("min set A", x, min.inputA.getValue()); - assertEquals("min set B", y, min.inputB.getValue()); - min.start(); - synthesisEngine.sleepFor(0.01); - - assertEquals("min output", y, min.output.getValue()); - synthesisEngine.stop(); - } - - /** if we use a port index out of range we want to know now and not blow up the engine. */ - public void testSetBadPort() throws InterruptedException { - SynthesisEngine synthesisEngine = new SynthesisEngine(); - synthesisEngine.setRealTime(false); - synthesisEngine.start(); - Minimum min; - synthesisEngine.add(min = new Minimum()); - - min.start(); - Exception caught = null; - try { - min.inputA.set(1, 23.45); - } catch (Exception e) { - caught = e; - } - assertTrue("Catch port out of range, caught " + caught, - (caught instanceof ArrayIndexOutOfBoundsException)); - - // Don't blow up here. - synthesisEngine.sleepUntil(0.01); - - synthesisEngine.stop(); - } - -} diff --git a/tests/com/jsyn/research/BenchMultiThreading.java b/tests/com/jsyn/research/BenchMultiThreading.java deleted file mode 100644 index 79b20bb..0000000 --- a/tests/com/jsyn/research/BenchMultiThreading.java +++ /dev/null @@ -1,143 +0,0 @@ -/* - * 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.research; - -import java.util.ArrayList; - -import junit.framework.TestCase; - -public class BenchMultiThreading extends TestCase { - private static final int FRAMES_PER_BLOCK = 64; - int numThreads = 4; - int numLoops = 100000; - private ArrayList threadList; - - class CustomThread extends Thread { - long frameCount = 0; - long desiredFrame = 0; - Object semaphore = new Object(); - Object goSemaphore = new Object(); - volatile boolean go = true; - long startNano; - long stopNano; - long maxElapsed; - - @Override - public void run() { - try { - startNano = System.nanoTime(); - while (go) { - // Watch for long delays. - stopNano = System.nanoTime(); - long elapsed = stopNano - startNano; - startNano = System.nanoTime(); - if (elapsed > maxElapsed) { - maxElapsed = elapsed; - } - - synchronized (semaphore) { - // Audio synthesis would occur here. - frameCount += 1; - // System.out.println( this + " generating frame " + - // frameCount ); - semaphore.notify(); - } - synchronized (goSemaphore) { - while (desiredFrame <= frameCount) { - goSemaphore.wait(); - } - } - long stopNano = System.nanoTime(); - } - } catch (InterruptedException e) { - System.out.println("CustomThread interrupted. "); - } - System.out.println("Finishing " + this); - } - - public void abort() { - go = false; - interrupt(); - } - - public void waitForFrame(long targetFrame) throws InterruptedException { - synchronized (semaphore) { - while (frameCount < targetFrame) { - semaphore.wait(); - } - } - } - - public void generateFrame(long desiredFrame) { - synchronized (goSemaphore) { - this.desiredFrame = desiredFrame; - goSemaphore.notify(); - } - } - - } - - public void testMultiThreads() { - threadList = new ArrayList(); - for (int i = 0; i < numThreads; i++) { - CustomThread thread = new CustomThread(); - threadList.add(thread); - thread.start(); - } - - long frameCount = 0; - long startTime = System.currentTimeMillis(); - try { - for (int i = 0; i < numLoops; i++) { - frameCount += 1; - waitForThreads(frameCount); - // System.out.println("got frame " + frameCount ); - } - } catch (InterruptedException e) { - e.printStackTrace(); - } - long stopTime = System.currentTimeMillis(); - long elapsedTime = stopTime - startTime; - double elapsedSeconds = 0.001 * elapsedTime; - double blocksPerSecond = numLoops / elapsedSeconds; - System.out.format("blocksPerSecond = %10.3f\n", blocksPerSecond); - double framesPerSecond = blocksPerSecond * FRAMES_PER_BLOCK; - System.out.format("audio framesPerSecond = %10.3f at %d frames per block\n", - framesPerSecond, FRAMES_PER_BLOCK); - - for (CustomThread thread : threadList) { - System.out.format("max elapsed time is %d nanos or %f msec\n", thread.maxElapsed, - (thread.maxElapsed / 1000000.0)); - } - for (CustomThread thread : threadList) { - assertEquals("BlockCount must match ", frameCount, thread.frameCount); - thread.abort(); - } - - } - - private void waitForThreads(long frameCount) throws InterruptedException { - for (CustomThread thread : threadList) { - // Ask threads to wake up and generate up to this frame. - thread.generateFrame(frameCount); - } - for (CustomThread thread : threadList) { - // Wait for all the threads to catch up. - thread.waitForFrame(frameCount); - } - } -} diff --git a/tests/com/jsyn/research/RecordVariousRamps.java b/tests/com/jsyn/research/RecordVariousRamps.java deleted file mode 100644 index c90ea9a..0000000 --- a/tests/com/jsyn/research/RecordVariousRamps.java +++ /dev/null @@ -1,183 +0,0 @@ -/* - * Copyright 2014 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. - */ -/** - * Generate steps, linear ramps and smooth ramps. - * - * @author (C) 2014 Phil Burk - */ - -package com.jsyn.research; - -import java.io.File; -import java.io.IOException; - -import com.jsyn.JSyn; -import com.jsyn.Synthesizer; -import com.jsyn.unitgen.ContinuousRamp; -import com.jsyn.unitgen.LineOut; -import com.jsyn.unitgen.LinearRamp; -import com.jsyn.unitgen.Multiply; -import com.jsyn.unitgen.PassThrough; -import com.jsyn.unitgen.PowerOfTwo; -import com.jsyn.unitgen.SawtoothOscillatorBL; -import com.jsyn.unitgen.UnitFilter; -import com.jsyn.unitgen.UnitOscillator; -import com.jsyn.util.WaveRecorder; - -public class RecordVariousRamps { - private Synthesizer synth; - private UnitOscillator osc; - private Multiply multiplier; - private UnitFilter ramp; - private LinearRamp linearRamp; - private ContinuousRamp continuousRamp; - private LineOut lineOut; - private WaveRecorder recorder; - private PowerOfTwo powerOfTwo; - private static final int MODE_STEP = 0; - private static final int MODE_LINEAR = 1; - private static final int MODE_SMOOTH = 2; - private static final String[] modeNames = { - "step", "linear", "smooth" - }; - - private RampEvent[] rampData = { - new RampEvent(1.0, 1.5, 2.0), new RampEvent(-0.9, 0.5, 1.0), - new RampEvent(0.9, 0.5, 0.8), new RampEvent(-0.3, 0.5, 0.8), - new RampEvent(0.9, 0.5, 0.3), new RampEvent(-0.5, 0.5, 0.3), - new RampEvent(0.8, 2.0, 1.0), - }; - - private static class RampEvent { - double target; - double eventDuration; - double rampDuration; - - RampEvent(double target, double eventDuration, double rampDuration) { - this.target = target; - this.eventDuration = eventDuration; - this.rampDuration = rampDuration; - } - } - - private void test(int mode) throws IOException { - // Create a context for the synthesizer. - synth = JSyn.createSynthesizer(); - synth.setRealTime(false); - - File waveFile = new File("ramp_pitch_" + modeNames[mode] + ".wav"); - // Mono 16 bits. - recorder = new WaveRecorder(synth, waveFile, 1, 16); - System.out.println("Writing to 16-bit WAV file " + waveFile.getAbsolutePath()); - - // Add some tone generators. - synth.add(osc = new SawtoothOscillatorBL()); - - // Add a controller that will sweep up. - synth.add(multiplier = new Multiply()); - synth.add(powerOfTwo = new PowerOfTwo()); - // Add an output unit. - synth.add(lineOut = new LineOut()); - multiplier.inputB.set(660.0); - - switch (mode) { - case MODE_STEP: - synth.add(ramp = new PassThrough()); - break; - case MODE_LINEAR: - synth.add(ramp = linearRamp = new LinearRamp()); - linearRamp.current.set(-1.0); - linearRamp.time.set(10.0); - break; - case MODE_SMOOTH: - synth.add(ramp = continuousRamp = new ContinuousRamp()); - continuousRamp.current.set(-1.0); - continuousRamp.time.set(10.0); - break; - } - - ramp.getInput().set(-1.0); - ramp.getOutput().connect(powerOfTwo.input); - - powerOfTwo.output.connect(multiplier.inputA); - multiplier.output.connect(osc.frequency); - - // Connect the oscillator to the left and right audio output. - osc.output.connect(0, lineOut.input, 0); - osc.output.connect(0, lineOut.input, 1); - - // Start synthesizer using default stereo output at 44100 Hz. - synth.start(); - - osc.output.connect(0, recorder.getInput(), 0); - // When we start the recorder it will pull data from the oscillator - // and sweeper. - recorder.start(); - - // We also need to start the LineOut if we want to hear it now. - lineOut.start(); - - // Get synthesizer time in seconds. - double nextEventTime = synth.getCurrentTime() + 1.0; - try { - synth.sleepUntil(nextEventTime); - } catch (InterruptedException e) { - e.printStackTrace(); - } - - for (RampEvent rampEvent : rampData) { - - switch (mode) { - case MODE_STEP: - break; - case MODE_LINEAR: - linearRamp.time.set(rampEvent.rampDuration); - break; - case MODE_SMOOTH: - continuousRamp.time.set(rampEvent.rampDuration); - break; - } - ramp.getInput().set(rampEvent.target); - - nextEventTime += rampEvent.eventDuration; - System.out.println("target = " + rampEvent.target + ", rampDur = " - + rampEvent.rampDuration + ", eventDur = " + rampEvent.eventDuration); - try { - synth.sleepUntil(nextEventTime); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - - if (recorder != null) { - recorder.stop(); - recorder.close(); - } - // Stop everything. - synth.stop(); - } - - public static void main(String[] args) { - try { - new RecordVariousRamps().test(MODE_STEP); - new RecordVariousRamps().test(MODE_LINEAR); - new RecordVariousRamps().test(MODE_SMOOTH); - } catch (IOException e) { - e.printStackTrace(); - } - } - -} diff --git a/tests/com/jsyn/research/lambdas/LambdaUnits.java b/tests/com/jsyn/research/lambdas/LambdaUnits.java deleted file mode 100644 index 42807ac..0000000 --- a/tests/com/jsyn/research/lambdas/LambdaUnits.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.jsyn.research.lambdas; - -import java.util.function.BinaryOperator; - -public class LambdaUnits { - - - public static void main(String[] args) { - test(); - } - - void tryLambda(BinaryOperator op) { - double result = op.apply(3.0, 4.0); - System.out.println("result = " + result); - } - - private static void test() { - System.out.println("Test Lambdas"); - // Need Java 8! tryLambda((x, y) -> (x * y)); - } - -} diff --git a/tests/com/jsyn/swing/TestRangeModels.java b/tests/com/jsyn/swing/TestRangeModels.java deleted file mode 100644 index 8bcd021..0000000 --- a/tests/com/jsyn/swing/TestRangeModels.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * 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.swing; - -import junit.framework.TestCase; - -public class TestRangeModels extends TestCase { - - @Override - protected void setUp() throws Exception { - super.setUp(); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - } - - public void checkDoubleRange(double dmin, double dmax, double dval) { - int resolution = 1000; - DoubleBoundedRangeModel model = new DoubleBoundedRangeModel("test", resolution, dmin, dmax, - dval); - assertEquals("setup min", dmin, model.getDoubleMinimum(), 0.0001); - assertEquals("setup max", dmax, model.getDoubleMaximum(), 0.0001); - assertEquals("setup value", dval, model.getDoubleValue(), 0.0001); - - model.setDoubleValue(dmin); - assertEquals("min double value", dmin, model.getDoubleValue(), 0.0001); - assertEquals("min value", 0, model.getValue()); - - double dmid = (dmax + dmin) / 2.0; - model.setDoubleValue(dmid); - assertEquals("middle double value", dmid, model.getDoubleValue(), 0.0001); - assertEquals("middle value", resolution / 2, model.getValue()); - - model.setDoubleValue(dmax); - assertEquals("max double value", dmax, model.getDoubleValue(), 0.0001); - assertEquals("max value", resolution, model.getValue()); - - } - - public void testDoubleRange() { - checkDoubleRange(10.0, 20.0, 12.0); - checkDoubleRange(-1.0, 1.0, 0.5); - } -} diff --git a/tests/com/jsyn/unitgen/CalibrateMoogFilter.java b/tests/com/jsyn/unitgen/CalibrateMoogFilter.java deleted file mode 100644 index a830fcc..0000000 --- a/tests/com/jsyn/unitgen/CalibrateMoogFilter.java +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Copyright 2010 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.unitgen; - -import javax.swing.JApplet; - -import com.jsyn.JSyn; -import com.jsyn.Synthesizer; - -/** - * Play a sawtooth through a 4-pole filter. - * - * @author Phil Burk (C) 2010 Mobileer Inc - */ -public class CalibrateMoogFilter extends JApplet { - private Synthesizer synth; - private UnitOscillator oscillator; - private SineOscillator reference; - ZeroCrossingCounter zeroCounter; - PitchDetector pitchDetector; - ZeroCrossingCounter sineZeroCounter; - PitchDetector sinePitchDetector; - private FilterFourPoles filterMoog; - private LineOut lineOut; - - @Override - public void init() { - synth = JSyn.createSynthesizer(); - synth.setRealTime(false); - synth.add(oscillator = new SawtoothOscillatorBL()); - synth.add(reference = new SineOscillator()); - synth.add(filterMoog = new FilterFourPoles()); - synth.add(pitchDetector = new PitchDetector()); - synth.add(sinePitchDetector = new PitchDetector()); - synth.add(zeroCounter = new ZeroCrossingCounter()); - synth.add(sineZeroCounter = new ZeroCrossingCounter()); - synth.add(lineOut = new LineOut()); - - oscillator.output.connect(filterMoog.input); - filterMoog.output.connect(zeroCounter.input); - zeroCounter.output.connect(pitchDetector.input); - reference.output.connect(0, lineOut.input, 0); - filterMoog.output.connect(0, lineOut.input, 1); - - reference.output.connect(sineZeroCounter.input); - sineZeroCounter.output.connect(sinePitchDetector.input); - - oscillator.frequency.set(130.0); - oscillator.amplitude.set(0.001); - filterMoog.frequency.set(440.0); - filterMoog.Q.set(4.1); - } - - @Override - public void start() { - // Start synthesizer using default stereo output at 44100 Hz. - synth.start(); - pitchDetector.start(); - sinePitchDetector.start(); - lineOut.start(); - } - - @Override - public void stop() { - pitchDetector.stop(); - sinePitchDetector.stop(); - lineOut.stop(); - synth.stop(); - } - - public void test() { - init(); - start(); - try { - calibrate(); - } catch (InterruptedException e) { - e.printStackTrace(); - } - stop(); - } - - private void calibrate() throws InterruptedException { - synth.sleepFor(2.0); - double freq = 100.0; - System.out - .printf("freq, moogFreq, ratio, moogConf, sineFreq, sineConf, moogZRate, sineZRate\n"); - long startingFrameCount = synth.getFrameCount(); - long startingMoogZeroCount = zeroCounter.getCount(); - long startingSineZeroCount = sineZeroCounter.getCount(); - for (int i = 0; i < 50; i++) { - reference.frequency.set(freq); - filterMoog.frequency.set(freq); - synth.sleepFor(2.0); - - long endingFrameCount = synth.getFrameCount(); - long elapsedFrames = endingFrameCount - startingFrameCount; - startingFrameCount = endingFrameCount; - - long endingMoogZeroCount = zeroCounter.getCount(); - long elapsedMoogZeros = endingMoogZeroCount - startingMoogZeroCount; - startingMoogZeroCount = endingMoogZeroCount; - - long endingSineZeroCount = sineZeroCounter.getCount(); - long elapsedSineZeros = endingSineZeroCount - startingSineZeroCount; - startingSineZeroCount = endingSineZeroCount; - - double moogZeroRate = elapsedMoogZeros * (double) synth.getFrameRate() / elapsedFrames; - double sineZeroRate = elapsedSineZeros * (double) synth.getFrameRate() / elapsedFrames; - - double moogMeasuredFreq = pitchDetector.frequency.get(); - double moogConfidence = pitchDetector.confidence.get(); - double sineMeasuredFreq = sinePitchDetector.frequency.get(); - double sineConfidence = sinePitchDetector.confidence.get(); - double ratio = freq / moogMeasuredFreq; - System.out.printf("%7.2f, %8.5f, %7.5f, %4.2f, %8.5f, %4.2f, %8.4f, %8.4f\n", freq, - moogMeasuredFreq, ratio, moogConfidence, sineMeasuredFreq, sineConfidence, - moogZeroRate, sineZeroRate); - - freq *= 1.1; - } - } - - public static void main(String args[]) { - new CalibrateMoogFilter().test(); - } - -} diff --git a/tests/com/jsyn/unitgen/EnablingGate.java b/tests/com/jsyn/unitgen/EnablingGate.java deleted file mode 100644 index daf36be..0000000 --- a/tests/com/jsyn/unitgen/EnablingGate.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2010 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.unitgen; - -import com.jsyn.ports.UnitInputPort; - -/** - * This can be used to block the execution of upstream units. It can be placed at the output of a - * circuit and driven with an amplitude envelope. - * - * @author Phil Burk (C) 2010 Mobileer Inc - */ -public class EnablingGate extends UnitFilter { - public UnitInputPort gate; - - /* Define Unit Ports used by connect() and set(). */ - public EnablingGate() { - super(); - addPort(gate = new UnitInputPort("Gate")); - } - - @Override - public void generate(int start, int limit) { - double[] aValues = input.getValues(); - double[] bValues = gate.getValues(); - double[] outputs = output.getValues(); - for (int i = start; i < limit; i++) { - outputs[i] = aValues[i] * bValues[i]; - } - // If we end up at zero then disable pulling of data. - // We do this at the end so that envelope can get started. - if (outputs[limit - 1] <= 0.0) { - setEnabled(false); - } - } - -} diff --git a/tests/com/jsyn/unitgen/NonRealTimeTestCase.java b/tests/com/jsyn/unitgen/NonRealTimeTestCase.java deleted file mode 100644 index 5d332a9..0000000 --- a/tests/com/jsyn/unitgen/NonRealTimeTestCase.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * 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.unitgen; - -import junit.framework.TestCase; - -import com.jsyn.engine.SynthesisEngine; - -public abstract class NonRealTimeTestCase extends TestCase { - - protected SynthesisEngine synthesisEngine; - - public NonRealTimeTestCase() { - super(); - } - - public NonRealTimeTestCase(String name) { - super(name); - } - - @Override - protected void setUp() throws Exception { - super.setUp(); - synthesisEngine = new SynthesisEngine(); - synthesisEngine.setRealTime(false); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - synthesisEngine.stop(); - } - -} diff --git a/tests/com/jsyn/unitgen/RecordMoogFilter.java b/tests/com/jsyn/unitgen/RecordMoogFilter.java deleted file mode 100644 index 6af11fd..0000000 --- a/tests/com/jsyn/unitgen/RecordMoogFilter.java +++ /dev/null @@ -1,153 +0,0 @@ -/* - * Copyright 2010 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.unitgen; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; - -import javax.swing.JApplet; - -import com.jsyn.JSyn; -import com.jsyn.Synthesizer; -import com.jsyn.util.WaveRecorder; - -/** - * Measure actual frequency as a function of input frequency and Q. - * - * @author Phil Burk (C) 2010 Mobileer Inc - */ -public class RecordMoogFilter extends JApplet { - private final static boolean SWEEP_Q = false; - private final static boolean SWEEP_FREQUENCY = true; - private final static int NUM_STEPS = 11; - - private final static double MIN_Q = 0.0; - private final static double DEFAULT_Q = 9.0; - private final static double MAX_Q = 10.0; - - private final static double MIN_FREQUENCY = 100.0; - private final static double DEFAULT_FREQUENCY = 500.0; - private final static double MAX_FREQUENCY = 4000.0; - - private Synthesizer synth; - private WhiteNoise source; - private SineOscillator reference; - private FilterFourPoles filterMoog; - private LineOut lineOut; - private WaveRecorder recorder; - - @Override - public void init() { - synth = JSyn.createSynthesizer(); - synth.setRealTime(false); - synth.add(source = new WhiteNoise()); - synth.add(filterMoog = new FilterFourPoles()); - synth.add(reference = new SineOscillator()); - synth.add(lineOut = new LineOut()); - - source.output.connect(filterMoog.input); - reference.output.connect(0, lineOut.input, 0); - filterMoog.output.connect(0, lineOut.input, 1); - - reference.amplitude.set(0.5); - source.amplitude.set(0.5); - filterMoog.frequency.set(DEFAULT_FREQUENCY); - filterMoog.Q.set(DEFAULT_Q); - - File waveFile = new File("temp_recording.wav"); - // Default is stereo, 16 bits. - try { - recorder = new WaveRecorder(synth, waveFile); - } catch (FileNotFoundException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - System.out.println("Writing to WAV file " + waveFile.getAbsolutePath()); - } - - @Override - public void start() { - // Start synthesizer using default stereo output at 44100 Hz. - synth.start(); - lineOut.start(); - - reference.output.connect(0, recorder.getInput(), 0); - filterMoog.output.connect(0, recorder.getInput(), 1); - recorder.start(); - } - - @Override - public void stop() { - if (recorder != null) { - recorder.stop(); - try { - recorder.close(); - } catch (IOException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - } - lineOut.stop(); - synth.stop(); - } - - public void test() { - init(); - start(); - try { - calibrate(); - } catch (InterruptedException e) { - e.printStackTrace(); - } - stop(); - } - - private void calibrate() throws InterruptedException { - synth.sleepFor(0.2); - double freq = SWEEP_FREQUENCY ? MIN_FREQUENCY : DEFAULT_FREQUENCY; - double q = SWEEP_Q ? MIN_Q : DEFAULT_Q; - double stepQ = (MAX_Q - MIN_Q) / (NUM_STEPS - 1); - double scaleFrequency = Math.pow((MAX_FREQUENCY / MIN_FREQUENCY), (1.0 / (NUM_STEPS - 1))); - System.out.printf("freq, q, measured\n"); - for (int i = 0; i < NUM_STEPS; i++) { - double refAmp = reference.amplitude.get(); - reference.amplitude.set(0.0); - synth.sleepFor(0.1); - reference.amplitude.set(refAmp); - - System.out.printf("%8.2f, %6.3f, \n", freq, q); - filterMoog.frequency.set(freq); - reference.frequency.set(freq); - filterMoog.Q.set(q); - - synth.sleepFor(2.0); - - if (SWEEP_FREQUENCY) { - freq *= scaleFrequency; - } - if (SWEEP_Q) { - q += stepQ; - } - } - } - - public static void main(String args[]) { - new RecordMoogFilter().test(); - } - -} diff --git a/tests/com/jsyn/unitgen/TestConnections.java b/tests/com/jsyn/unitgen/TestConnections.java deleted file mode 100644 index d15a257..0000000 --- a/tests/com/jsyn/unitgen/TestConnections.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * 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.unitgen; - -import junit.framework.TestCase; - -import com.jsyn.JSyn; -import com.jsyn.Synthesizer; - -public class TestConnections extends TestCase { - Add add1; - Add add2; - Add add3; - - Synthesizer synth; - - @Override - protected void setUp() throws Exception { - super.setUp(); - synth = JSyn.createSynthesizer(); - - synth.add(add1 = new Add()); - synth.add(add2 = new Add()); - synth.add(add3 = new Add()); - - add1.start(); - add2.start(); - add3.start(); - - add1.inputA.set(0.1); - add1.inputB.set(0.2); - - add2.inputA.set(0.4); - add2.inputB.set(0.8); - - add3.inputA.set(1.6); - add3.inputB.set(3.2); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - } - - public void testSet() throws InterruptedException { - synth.sleepFor(0.01); - assertEquals("set inputs of adder", 0.3, add1.output.getValue(), 0.0001); - } - - public void testConnect() throws InterruptedException { - synth.sleepFor(0.01); - assertEquals("set inputs of adder", 0.3, add1.output.getValue(), 0.0001); - assertEquals("set inputs of adder", 1.2, add2.output.getValue(), 0.0001); - - // Test different ways of connecting. - add1.output.connect(add2.inputB); - checkConnection(); - - add1.output.connect(0, add2.inputB, 0); - checkConnection(); - - add1.output.connect(add2.inputB.getConnectablePart(0)); - checkConnection(); - - add1.output.getConnectablePart(0).connect(add2.inputB); - checkConnection(); - - add1.output.getConnectablePart(0).connect(add2.inputB.getConnectablePart(0)); - checkConnection(); - - add2.inputB.connect(add1.output); - checkConnection(); - - add2.inputB.connect(0, add1.output, 0); - checkConnection(); - - add2.inputB.connect(add1.output.getConnectablePart(0)); - checkConnection(); - - add2.inputB.getConnectablePart(0).connect(add1.output); - checkConnection(); - - add2.inputB.getConnectablePart(0).connect(add1.output.getConnectablePart(0)); - checkConnection(); - } - - private void checkConnection() throws InterruptedException { - synth.sleepFor(0.01); - assertEquals("connection should not change output", 0.3, add1.output.getValue(), 0.0001); - assertEquals("replace set value with output", 0.7, add2.output.getValue(), 0.0001); - - // Revert to set value after disconnection. - add1.output.disconnectAll(); - synth.sleepFor(0.01); - assertEquals("still the same", 0.3, add1.output.getValue(), 0.0001); - assertEquals("should revert to original set() value", 1.2, add2.output.getValue(), 0.0001); - } - -} diff --git a/tests/com/jsyn/unitgen/TestDelay.java b/tests/com/jsyn/unitgen/TestDelay.java deleted file mode 100644 index 12af1cd..0000000 --- a/tests/com/jsyn/unitgen/TestDelay.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * 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.unitgen; - -import com.jsyn.util.AudioStreamReader; - -public class TestDelay extends NonRealTimeTestCase { - public void testFloor() { - double x = -7.3; - int n = (int) Math.floor(x); - assertEquals("int", -8, n); - } - - public void checkInterpolatingDelay(int maxFrames, double delayFrames) - throws InterruptedException { - synthesisEngine.start(); - - System.out.printf("test delayFrames = %7.5f\n", delayFrames); - InterpolatingDelay delay = new InterpolatingDelay(); - synthesisEngine.add(delay); - delay.allocate(maxFrames); - delay.delay.set(delayFrames / 44100.0); - SawtoothOscillator osc = new SawtoothOscillator(); - synthesisEngine.add(osc); - osc.frequency.set(synthesisEngine.getFrameRate() / 4.0); - osc.amplitude.set(1.0); - osc.output.connect(delay.input); - - int samplesPerFrame = 1; - AudioStreamReader reader = new AudioStreamReader(synthesisEngine, samplesPerFrame); - delay.output.connect(reader.getInput()); - - delay.start(); - for (int i = 0; i < (3 * maxFrames); i++) { - if (reader.available() == 0) { - synthesisEngine.sleepFor(0.01); - } - double actual = reader.read(); - double expected = 1 + i - delayFrames; - if (expected < 0.0) { - expected = 0.0; - } - // System.out.printf( "[%d] expected = %7.3f, delayed = %7.3f\n", i, expected, actual ); - // assertEquals("delayed output", expected, actual, 0.00001); - } - } - - public void testSmall() throws InterruptedException { - checkInterpolatingDelay(40, 7.0); - } - - public void testEven() throws InterruptedException { - checkInterpolatingDelay(44100, 13671.0); - } - - public void testInterpolatingDelay() throws InterruptedException { - checkInterpolatingDelay(44100, 13671.4); - } -} diff --git a/tests/com/jsyn/unitgen/TestEnable.java b/tests/com/jsyn/unitgen/TestEnable.java deleted file mode 100644 index 37a4a2b..0000000 --- a/tests/com/jsyn/unitgen/TestEnable.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * 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.unitgen; - -import junit.framework.TestCase; - -import com.jsyn.engine.SynthesisEngine; - -public class TestEnable extends TestCase { - SynthesisEngine synthesisEngine; - - @Override - protected void setUp() throws Exception { - super.setUp(); - synthesisEngine = new SynthesisEngine(); - synthesisEngine.setRealTime(false); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - synthesisEngine.stop(); - } - - public void testEnablingGate() throws InterruptedException { - LinearRamp ramp = new LinearRamp(); - synthesisEngine.add(ramp); - EnablingGate enabler = new EnablingGate(); - synthesisEngine.add(enabler); - Add adder = new Add(); - synthesisEngine.add(adder); - - ramp.output.connect(enabler.input); - enabler.output.connect(adder.inputA); - - // set up so ramp should equal time - ramp.current.set(0.0); - ramp.input.set(1.0); - ramp.time.set(1.0); - enabler.gate.set(1.0); - - synthesisEngine.start(); - double startTime = synthesisEngine.getCurrentTime(); - // pull from final adder - adder.start(); - synthesisEngine.sleepUntil(startTime + 0.1); - double tolerance = 0.002; - assertEquals("ramp going up", 0.1, ramp.output.getValue(), tolerance); - assertEquals("enabler going up", 0.1, enabler.output.getValue(), tolerance); - assertEquals("adder going up", 0.1, adder.output.getValue(), tolerance); - synthesisEngine.sleepUntil(startTime + 0.2); - assertEquals("start enabled", 0.2, adder.output.getValue(), tolerance); - - // disable everything upstream - enabler.gate.set(0.0); - - synthesisEngine.sleepUntil(startTime + 0.3); - assertEquals("should not be pulled", 0.2, ramp.output.getValue(), tolerance); - assertEquals("should be disabled", false, enabler.isEnabled()); - assertEquals("should be zero", 0.0, enabler.output.getValue(), tolerance); - assertEquals("zero", 0.0, adder.output.getValue(), tolerance); - - } -} diff --git a/tests/com/jsyn/unitgen/TestEnvelopeAttackDecay.java b/tests/com/jsyn/unitgen/TestEnvelopeAttackDecay.java deleted file mode 100644 index 50ecb15..0000000 --- a/tests/com/jsyn/unitgen/TestEnvelopeAttackDecay.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * 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.unitgen; - -import com.jsyn.engine.SynthesisEngine; - -public class TestEnvelopeAttackDecay extends TestUnitGate { - double attackTime; - double decayTime; - - @Override - protected void setUp() throws Exception { - super.setUp(); - synthesisEngine = new SynthesisEngine(); - synthesisEngine.setRealTime(false); - attackTime = 0.2; - decayTime = 0.4; - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - synthesisEngine.stop(); - } - - public void testOnOff() throws InterruptedException { - EnvelopeAttackDecay envelope = new EnvelopeAttackDecay(); - synthesisEngine.add(envelope); - - envelope.attack.set(0.1); - envelope.decay.set(0.2); - - synthesisEngine.start(); - envelope.start(); - time = synthesisEngine.getCurrentTime(); - synthesisEngine.sleepUntil(time + 0.1); - assertEquals("still idling", 0.0, envelope.output.getValue()); - - // Trigger the envelope using on/off - envelope.input.on(); - time = synthesisEngine.getCurrentTime(); - // Check end of attack cycle. - synthesisEngine.sleepUntil(time + 0.1); - assertTrue("at peak", (envelope.output.getValue() > 0.8)); - envelope.input.off(); - // Check end of decay cycle. - synthesisEngine.sleepUntil(time + 0.3); - assertTrue("at peak", (envelope.output.getValue() < 0.1)); - - synthesisEngine.sleepFor(0.1); - - // Trigger the envelope using trigger() - envelope.input.trigger(); - time = synthesisEngine.getCurrentTime(); - // Check end of attack cycle. - synthesisEngine.sleepUntil(time + 0.1); - assertTrue("at peak", (envelope.output.getValue() > 0.8)); - // Check end of decay cycle. - synthesisEngine.sleepUntil(time + 0.3); - assertTrue("at peak", (envelope.output.getValue() < 0.1)); - - } - - public void testRetrigger() throws InterruptedException { - EnvelopeAttackDecay envelope = new EnvelopeAttackDecay(); - synthesisEngine.add(envelope); - - envelope.attack.set(0.1); - envelope.decay.set(0.2); - - synthesisEngine.start(); - envelope.start(); - time = synthesisEngine.getCurrentTime(); - synthesisEngine.sleepUntil(time + 0.1); - assertEquals("still idling", 0.0, envelope.output.getValue()); - - // Trigger the envelope using trigger() - envelope.input.trigger(); - // Check end of attack cycle. - synthesisEngine.sleepFor(0.1); - assertEquals("at peak", 1.0, envelope.output.getValue(), 0.1); - - // Decay half way. - synthesisEngine.sleepFor(0.1); - assertTrue("at peak", (envelope.output.getValue() < 0.7)); - - // Retrigger while decaying - envelope.input.trigger(); - // Will get to top faster. - synthesisEngine.sleepFor(0.1); - assertEquals("at peak", 1.0, envelope.output.getValue(), 0.1); - - // Check end of decay cycle. - synthesisEngine.sleepFor(0.2); - assertTrue("at peak", (envelope.output.getValue() < 0.1)); - - } - - public void testAutoDisable() throws InterruptedException { - - LinearRamp ramp = new LinearRamp(); - synthesisEngine.add(ramp); - EnvelopeAttackDecay envelope = new EnvelopeAttackDecay(); - envelope.attack.set(0.1); - envelope.decay.set(0.1); - synthesisEngine.add(envelope); - ramp.output.connect(envelope.amplitude); - - checkAutoDisable(ramp, envelope); - - } -} diff --git a/tests/com/jsyn/unitgen/TestEnvelopeDAHDSR.java b/tests/com/jsyn/unitgen/TestEnvelopeDAHDSR.java deleted file mode 100644 index 8c781ac..0000000 --- a/tests/com/jsyn/unitgen/TestEnvelopeDAHDSR.java +++ /dev/null @@ -1,339 +0,0 @@ -/* - * 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.unitgen; - -import com.jsyn.engine.SynthesisEngine; - -public class TestEnvelopeDAHDSR extends TestUnitGate { - double delayTime; - double attackTime; - double holdTime; - double decayTime; - double sustainLevel; - double releaseTime; - - @Override - protected void setUp() throws Exception { - super.setUp(); - synthesisEngine = new SynthesisEngine(); - synthesisEngine.setRealTime(false); - delayTime = 0.1; - attackTime = 0.2; - holdTime = 0.3; - decayTime = 0.4; - sustainLevel = 0.5; - releaseTime = 0.6; - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - synthesisEngine.stop(); - } - - public void testStages() throws InterruptedException { - EnvelopeDAHDSR ramp = checkToSustain(); - - // Change sustain level to simulate tremolo sustain. - sustainLevel = 0.7; - ramp.sustain.set(sustainLevel); - time += 0.01; - synthesisEngine.sleepUntil(time); - assertEquals("sustain moving delaying", sustainLevel, ramp.output.getValue(), 0.01); - - // Gate off to let envelope release. - ramp.input.set(0.0); - synthesisEngine.sleepUntil(time + (releaseTime * 0.1)); - double releaseValue = ramp.output.getValue(); - assertEquals("partway down release", sustainLevel * 0.36, releaseValue, 0.01); - } - - private EnvelopeDAHDSR checkToSustain() throws InterruptedException { - EnvelopeDAHDSR ramp = new EnvelopeDAHDSR(); - synthesisEngine.add(ramp); - - ramp.delay.set(delayTime); - ramp.attack.set(attackTime); - ramp.hold.set(holdTime); - ramp.decay.set(decayTime); - ramp.sustain.set(sustainLevel); - ramp.release.set(releaseTime); - - synthesisEngine.start(); - ramp.start(); - time = synthesisEngine.getCurrentTime(); - synthesisEngine.sleepUntil(time + (2.0 * delayTime)); - assertEquals("still idling", 0.0, ramp.output.getValue()); - - // Trigger the envelope. - ramp.input.set(1.0); - time = synthesisEngine.getCurrentTime(); - // Check end of delay cycle. - synthesisEngine.sleepUntil(time + (delayTime * 0.9)); - assertEquals("still delaying", 0.0, ramp.output.getValue(), 0.01); - // Half way up attack ramp. - synthesisEngine.sleepUntil(time + delayTime + (attackTime * 0.5)); - assertEquals("half attack", 0.5, ramp.output.getValue(), 0.01); - // Holding after attack. - synthesisEngine.sleepUntil(time + delayTime + attackTime + (holdTime * 0.1)); - assertEquals("holding", 1.0, ramp.output.getValue(), 0.01); - synthesisEngine.sleepUntil(time + delayTime + attackTime + (holdTime * 0.9)); - assertEquals("still holding", 1.0, ramp.output.getValue(), 0.01); - synthesisEngine.sleepUntil(time + delayTime + attackTime + holdTime + decayTime); - time = synthesisEngine.getCurrentTime(); - assertEquals("at sustain", sustainLevel, ramp.output.getValue(), 0.01); - return ramp; - } - - public void testRetrigger() throws InterruptedException { - EnvelopeDAHDSR ramp = checkToSustain(); - - // Gate off to let envelope release. - ramp.input.set(0.0); - synthesisEngine.sleepUntil(time + (releaseTime * 0.1)); - double releaseValue = ramp.output.getValue(); - assertEquals("partway down release", sustainLevel * 0.36, releaseValue, 0.01); - - // Retrigger during release phase. - time = synthesisEngine.getCurrentTime(); - ramp.input.set(1.0); - // Check end of delay cycle. - synthesisEngine.sleepUntil(time + (delayTime * 0.9)); - assertEquals("still delaying", releaseValue, ramp.output.getValue(), 0.01); - // Half way up attack ramp from where it started. - synthesisEngine.sleepUntil(time + delayTime + (attackTime * 0.5)); - assertEquals("half attack", releaseValue + 0.5, ramp.output.getValue(), 0.01); - - } - - // I noticed a hang while playing with knobs. - public void testHang() throws InterruptedException { - - delayTime = 0.0; - attackTime = 0.0; - holdTime = 0.0; - decayTime = 0.0; - sustainLevel = 0.3; - releaseTime = 3.0; - - EnvelopeDAHDSR ramp = new EnvelopeDAHDSR(); - synthesisEngine.add(ramp); - - ramp.delay.set(delayTime); - ramp.attack.set(attackTime); - ramp.hold.set(holdTime); - ramp.decay.set(decayTime); - ramp.sustain.set(sustainLevel); - ramp.release.set(releaseTime); - - synthesisEngine.start(); - ramp.start(); - // Trigger the envelope. - ramp.input.set(1.0); - time = synthesisEngine.getCurrentTime(); - synthesisEngine.sleepUntil(time + 0.01); - assertEquals("should jump to sustain level", sustainLevel, ramp.output.getValue()); - - // Gate off to let envelope release. - ramp.input.set(0.0); - synthesisEngine.sleepUntil(time + 1.0); - double releaseValue = ramp.output.getValue(); - assertTrue("partway down release", sustainLevel > releaseValue); - - holdTime = 0.5; - ramp.hold.set(holdTime); - decayTime = 0.5; - ramp.decay.set(decayTime); - - // Retrigger during release phase and try to catch it at top of hold - time = synthesisEngine.getCurrentTime(); - ramp.input.set(1.0); - // Check end of delay cycle. - synthesisEngine.sleepUntil(time + (holdTime * 0.1)); - assertEquals("should jump to hold", 1.0, ramp.output.getValue(), 0.01); - } - - public void testNegative() throws InterruptedException { - delayTime = -0.1; - attackTime = -0.2; - holdTime = -0.3; - decayTime = -0.4; - sustainLevel = 0.3; - releaseTime = -0.5; - - EnvelopeDAHDSR ramp = new EnvelopeDAHDSR(); - synthesisEngine.add(ramp); - - ramp.delay.set(delayTime); - ramp.attack.set(attackTime); - ramp.hold.set(holdTime); - ramp.decay.set(decayTime); - ramp.sustain.set(sustainLevel); - ramp.release.set(releaseTime); - - synthesisEngine.start(); - ramp.start(); - // Trigger the envelope. - ramp.input.set(1.0); - time = synthesisEngine.getCurrentTime(); - time += 0.1; - synthesisEngine.sleepUntil(time + 0.01); - assertEquals("should jump to sustain level", sustainLevel, ramp.output.getValue()); - - ramp.sustain.set(sustainLevel = -0.4); - time += 0.1; - synthesisEngine.sleepUntil(time); - assertEquals("sustain should clip at zero", sustainLevel, ramp.output.getValue()); - - ramp.sustain.set(sustainLevel = 0.4); - time += 0.1; - synthesisEngine.sleepUntil(time); - assertEquals("sustain should come back", sustainLevel, ramp.output.getValue()); - - // Gate off to let envelope release. - ramp.input.set(0.0); - time += 0.1; - synthesisEngine.sleepUntil(time); - double releaseValue = ramp.output.getValue(); - assertEquals("release quickly", 0.0, releaseValue); - } - - public void testOnOff() throws InterruptedException { - EnvelopeDAHDSR ramp = new EnvelopeDAHDSR(); - synthesisEngine.add(ramp); - - ramp.delay.set(0.0); - ramp.attack.set(0.1); - ramp.hold.set(0.0); - ramp.decay.set(0.0); - ramp.sustain.set(0.9); - ramp.release.set(0.1); - - synthesisEngine.start(); - ramp.start(); - time = synthesisEngine.getCurrentTime(); - synthesisEngine.sleepUntil(time + 0.2); - assertEquals("still idling", 0.0, ramp.output.getValue()); - - // Trigger the envelope. - ramp.input.on(); - time = synthesisEngine.getCurrentTime(); - // Check end of delay cycle. - synthesisEngine.sleepUntil(time + 0.2); - assertEquals("at sustain", 0.9, ramp.output.getValue(), 0.01); - - // Release the envelope. - ramp.input.off(); - time = synthesisEngine.getCurrentTime(); - // Check end of delay cycle. - synthesisEngine.sleepUntil(time + 0.2); - assertEquals("after release", 0.0, ramp.output.getValue(), 0.01); - } - - public void testAutoDisable() throws InterruptedException { - - LinearRamp ramp = new LinearRamp(); - synthesisEngine.add(ramp); - EnvelopeDAHDSR envelope = new EnvelopeDAHDSR(); - synthesisEngine.add(envelope); - envelope.attack.set(0.1); - envelope.decay.set(0.1); - envelope.release.set(0.1); - envelope.sustain.set(0.1); - ramp.output.connect(envelope.amplitude); - - checkAutoDisable(ramp, envelope); - - } - - class GatedRampCircuit extends Circuit { - LinearRamp ramp; - EnvelopeDAHDSR envelope; - - GatedRampCircuit() { - add(ramp = new LinearRamp()); - add(envelope = new EnvelopeDAHDSR()); - envelope.attack.set(0.1); - envelope.decay.set(0.1); - envelope.release.set(0.1); - envelope.sustain.set(0.1); - - envelope.setupAutoDisable(this); - ramp.output.connect(envelope.amplitude); - } - } - - public void testAutoDisableCircuit() throws InterruptedException { - GatedRampCircuit circuit = new GatedRampCircuit(); - synthesisEngine.add(circuit); - checkAutoDisable(circuit.ramp, circuit.envelope); - } - - public void checkReleaseTiming(double releaseTime, double tolerance) - throws InterruptedException { - delayTime = 0.0; - attackTime = 0.2; - holdTime = 0.0; - decayTime = 10.0; - sustainLevel = 1.0; - - EnvelopeDAHDSR ramp = new EnvelopeDAHDSR(); - synthesisEngine.add(ramp); - - ramp.delay.set(delayTime); - ramp.attack.set(attackTime); - ramp.hold.set(holdTime); - ramp.decay.set(decayTime); - ramp.sustain.set(sustainLevel); - ramp.release.set(releaseTime); - - synthesisEngine.start(); - ramp.start(); - // Trigger the envelope. - ramp.input.set(1.0); - time = synthesisEngine.getCurrentTime(); - time += attackTime * 2; - synthesisEngine.sleepUntil(time); - assertEquals("should be at to sustain level", sustainLevel, ramp.output.getValue()); - - // Start envelope release. - ramp.input.set(0.0); - final double db90 = 20.0 * Math.log(1.0 / 32768.0) / Math.log(10.0); - System.out.println("JSyns DB90 is actually " + db90); - int numSteps = 10; - for (int i = 0; i < 10; i++) { - time += releaseTime / numSteps; - synthesisEngine.sleepUntil(time); - double expectedDB = db90 * (i + 1) / numSteps; - double expectedAmplitude = sustainLevel * Math.pow(10.0, expectedDB / 20.0); - double releaseValue = ramp.output.getValue(); - assertEquals("release " + i + " at", expectedAmplitude, releaseValue, tolerance); - } - time += releaseTime / numSteps; - synthesisEngine.sleepUntil(time); - double releaseValue = ramp.output.getValue(); - assertEquals("env after release time should go to zero", 0.0, releaseValue, 0.0001); - } - - public void testReleaseTiming() throws InterruptedException { - checkReleaseTiming(0.1, 0.004); - checkReleaseTiming(1.0, 0.002); - checkReleaseTiming(2.5, 0.001); - checkReleaseTiming(10.0, 0.001); - } - -} diff --git a/tests/com/jsyn/unitgen/TestFunction.java b/tests/com/jsyn/unitgen/TestFunction.java deleted file mode 100644 index a8bfac0..0000000 --- a/tests/com/jsyn/unitgen/TestFunction.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * 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.unitgen; - -import junit.framework.TestCase; - -import com.jsyn.JSyn; -import com.jsyn.Synthesizer; -import com.jsyn.data.DoubleTable; -import com.jsyn.data.Function; - -/** - * @author Phil Burk, (C) 2009 Mobileer Inc - */ -public class TestFunction extends TestCase { - Synthesizer synth; - - @Override - protected void setUp() throws Exception { - super.setUp(); - synth = JSyn.createSynthesizer(); - synth.setRealTime(false); - synth.start(); - } - - @Override - protected void tearDown() throws Exception { - synth.stop(); - } - - public void testDoubleTable() { - double[] data = { - 2.0, 0.0, 3.0 - }; - DoubleTable table = new DoubleTable(data); - assertEquals("DoubleTable below", 2.0, table.evaluate(-1.4)); - assertEquals("DoubleTable edge", 2.0, table.evaluate(-1.0)); - assertEquals("DoubleTable mid", 1.0, table.evaluate(-0.5)); - assertEquals("DoubleTable zero", 0.0, table.evaluate(0.0)); - assertEquals("DoubleTable mid", 0.75, table.evaluate(0.25)); - assertEquals("DoubleTable above", 3.0, table.evaluate(1.3)); - - } - - public void testFunctionEvaluator() throws InterruptedException { - FunctionEvaluator shaper = new FunctionEvaluator(); - synth.add(shaper); - shaper.start(); - - Function cuber = new Function() { - @Override - public double evaluate(double x) { - return x * x * x; - } - }; - shaper.function.set(cuber); - - shaper.input.set(0.5); - synth.sleepFor(0.001); - - assertEquals("Cuber", (0.5 * 0.5 * 0.5), shaper.output.getValue()); - } - -} diff --git a/tests/com/jsyn/unitgen/TestMath.java b/tests/com/jsyn/unitgen/TestMath.java deleted file mode 100644 index cae1dea..0000000 --- a/tests/com/jsyn/unitgen/TestMath.java +++ /dev/null @@ -1,413 +0,0 @@ -/* - * 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.unitgen; - -import junit.framework.TestCase; - -import com.jsyn.engine.SynthesisEngine; -import com.softsynth.math.AudioMath; - -/** - * @author Phil Burk, (C) 2009 Mobileer Inc - */ -public class TestMath extends TestCase { - SynthesisEngine synthesisEngine; - - @Override - protected void setUp() throws Exception { - super.setUp(); - synthesisEngine = new SynthesisEngine(); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - } - - public void testAdd() { - Add add = new Add(); - add.setSynthesisEngine(synthesisEngine); - - double x = 33.99; - double y = 8.31; - add.inputA.setValueInternal(x); - add.inputB.setValueInternal(y); - - add.generate(); - - assertEquals("Add", x + y, add.output.getValue(), 0.001); - } - - public void testPartialAdd() { - Add add = new Add(); - add.setSynthesisEngine(synthesisEngine); - - double x = 2.5; - double y = 9.7; - add.inputA.setValueInternal(x); - add.inputB.setValueInternal(y); - - // Only generate a few values in the middle. - // This is to test low latency feedback loops. - // Only generate values for 2,3,4 - add.generate(2, 5); - - assertEquals("Add partial", 0.0, add.output.getValues()[0], 0.001); - assertEquals("Add partial", 0.0, add.output.getValues()[1], 0.001); - assertEquals("Add partial", x + y, add.output.getValues()[2], 0.001); - assertEquals("Add partial", x + y, add.output.getValues()[3], 0.001); - assertEquals("Add partial", x + y, add.output.getValues()[4], 0.001); - assertEquals("Add partial", 0.0, add.output.getValues()[5], 0.001); - assertEquals("Add partial", 0.0, add.output.getValues()[6], 0.001); - assertEquals("Add partial", 0.0, add.output.getValues()[7], 0.001); - - } - - /** - * Unit test for Subtract.java - added by Lisa Tolentino 06/17/2009 - */ - public void testSubtract() { - Subtract sub = new Subtract(); - sub.setSynthesisEngine(synthesisEngine); - - double x = 33.99; - double y = 8.31; - sub.inputA.setValueInternal(x); - sub.inputB.setValueInternal(y); - - sub.generate(); - - assertEquals("Subtract", x - y, sub.output.getValue(), 0.001); - } - - public void testPartialSubtract() { - Subtract sub = new Subtract(); - sub.setSynthesisEngine(synthesisEngine); - - double x = 2.5; - double y = 9.7; - sub.inputA.setValueInternal(x); - sub.inputB.setValueInternal(y); - - // Only generate a few values in the middle. - // This is to test low latency feedback loops. - // Only generate values for 2,3,4 - sub.generate(2, 5); - - assertEquals("Subtract partial", 0.0, sub.output.getValues()[0], 0.001); - assertEquals("Subtract partial", 0.0, sub.output.getValues()[1], 0.001); - assertEquals("Subtract partial", x - y, sub.output.getValues()[2], 0.001); - assertEquals("Subtract partial", x - y, sub.output.getValues()[3], 0.001); - assertEquals("Subtract partial", x - y, sub.output.getValues()[4], 0.001); - assertEquals("Subtract partial", 0.0, sub.output.getValues()[5], 0.001); - assertEquals("Subtract partial", 0.0, sub.output.getValues()[6], 0.001); - assertEquals("Subtract partial", 0.0, sub.output.getValues()[7], 0.001); - - } - - /** - * Unit test for Multiply.java - added by Lisa Tolentino 06/19/2009 - */ - public void testMultiply() { - Multiply mult = new Multiply(); - mult.setSynthesisEngine(synthesisEngine); - - double x = 33.99; - double y = 8.31; - mult.inputA.setValueInternal(x); - mult.inputB.setValueInternal(y); - - mult.generate(); - - assertEquals("Multiply", x * y, mult.output.getValue(), 0.001); - } - - public void testPartialMultiply() { - Multiply mult = new Multiply(); - mult.setSynthesisEngine(synthesisEngine); - - double x = 2.5; - double y = 9.7; - mult.inputA.setValueInternal(x); - mult.inputB.setValueInternal(y); - - // Only generate a few values in the middle. - // This is to test low latency feedback loops. - // Only generate values for 2,3,4 - mult.generate(2, 5); - - assertEquals("Multiply partial", 0.0, mult.output.getValues()[0], 0.001); - assertEquals("Multiply partial", 0.0, mult.output.getValues()[1], 0.001); - assertEquals("Multiply partial", x * y, mult.output.getValues()[2], 0.001); - assertEquals("Multiply partial", x * y, mult.output.getValues()[3], 0.001); - assertEquals("Multiply partial", x * y, mult.output.getValues()[4], 0.001); - assertEquals("Multiply partial", 0.0, mult.output.getValues()[5], 0.001); - assertEquals("Multiply partial", 0.0, mult.output.getValues()[6], 0.001); - assertEquals("Multiply partial", 0.0, mult.output.getValues()[7], 0.001); - - } - - /** - * Unit test for Divide.java - added by Lisa Tolentino 06/19/2009 - */ - public void testDivide() { - Divide divide = new Divide(); - divide.setSynthesisEngine(synthesisEngine); - - double x = 33.99; - double y = 8.31; - divide.inputA.setValueInternal(x); - divide.inputB.setValueInternal(y); - - divide.generate(); - - assertEquals("Divide", x / y, divide.output.getValue(), 0.001); - } - - public void testPartialDivide() { - Divide divide = new Divide(); - divide.setSynthesisEngine(synthesisEngine); - - double x = 2.5; - double y = 9.7; - divide.inputA.setValueInternal(x); - divide.inputB.setValueInternal(y); - - // Only generate a few values in the middle. - // This is to test low latency feedback loops. - // Only generate values for 2,3,4 - divide.generate(2, 5); - - assertEquals("Divide partial", 0.0, divide.output.getValues()[0], 0.001); - assertEquals("Divide partial", 0.0, divide.output.getValues()[1], 0.001); - assertEquals("Divide partial", x / y, divide.output.getValues()[2], 0.001); - assertEquals("Divide partial", x / y, divide.output.getValues()[3], 0.001); - assertEquals("Divide partial", x / y, divide.output.getValues()[4], 0.001); - assertEquals("Divide partial", 0.0, divide.output.getValues()[5], 0.001); - assertEquals("Divide partial", 0.0, divide.output.getValues()[6], 0.001); - assertEquals("Divide partial", 0.0, divide.output.getValues()[7], 0.001); - - } - - /** - * Unit test for MultiplyAdd.java - added by Lisa Tolentino 06/19/2009 - */ - public void testMultiplyAdd() { - MultiplyAdd multAdd = new MultiplyAdd(); - multAdd.setSynthesisEngine(synthesisEngine); - - double x = 33.99; - double y = 8.31; - double z = 2.28; - multAdd.inputA.setValueInternal(x); - multAdd.inputB.setValueInternal(y); - multAdd.inputC.setValueInternal(z); - - multAdd.generate(); - - assertEquals("MultiplyAdd", (x * y) + z, multAdd.output.getValue(), 0.001); - } - - public void testPartialMultiplyAdd() { - MultiplyAdd multAdd = new MultiplyAdd(); - multAdd.setSynthesisEngine(synthesisEngine); - - double x = 33.99; - double y = 8.31; - double z = 2.28; - multAdd.inputA.setValueInternal(x); - multAdd.inputB.setValueInternal(y); - multAdd.inputC.setValueInternal(z); - - // Only generate a few values in the middle. - // This is to test low latency feedback loops. - // Only generate values for 2,3,4 - multAdd.generate(2, 5); - - assertEquals("MultiplyAdd partial", 0.0, multAdd.output.getValues()[0], 0.001); - assertEquals("MultiplyAdd partial", 0.0, multAdd.output.getValues()[1], 0.001); - assertEquals("MultiplyAdd partial", (x * y) + z, multAdd.output.getValues()[2], 0.001); - assertEquals("MultiplyAdd partial", (x * y) + z, multAdd.output.getValues()[3], 0.001); - assertEquals("MultiplyAdd partial", (x * y) + z, multAdd.output.getValues()[4], 0.001); - assertEquals("MultiplyAdd partial", 0.0, multAdd.output.getValues()[5], 0.001); - assertEquals("MultiplyAdd partial", 0.0, multAdd.output.getValues()[6], 0.001); - assertEquals("MultiplyAdd partial", 0.0, multAdd.output.getValues()[7], 0.001); - - } - - /** - * Unit test for Compare.java - added by Lisa Tolentino 06/19/2009 - */ - public void testCompare() { - UnitBinaryOperator compare = new Compare(); - compare.setSynthesisEngine(synthesisEngine); - - double x = 33.99; - double y = 8.31; - compare.inputA.setValueInternal(x); - compare.inputB.setValueInternal(y); - - compare.generate(); - - assertEquals("Compare", (x > y ? 1 : 0), compare.output.getValue(), 0.001); - } - - public void testPartialCompare() { - UnitBinaryOperator compare = new Compare(); - compare.setSynthesisEngine(synthesisEngine); - - double x = 33.99; - double y = 8.31; - compare.inputA.setValueInternal(x); - compare.inputB.setValueInternal(y); - - // Only generate a few values in the middle. - // This is to test low latency feedback loops. - // Only generate values for 2,3,4 - compare.generate(2, 5); - - assertEquals("Compare partial", 0.0, compare.output.getValues()[0], 0.001); - assertEquals("Compare partial", 0.0, compare.output.getValues()[1], 0.001); - assertEquals("Compare partial", (x > y ? 1 : 0), compare.output.getValues()[2], 0.001); - assertEquals("Compare partial", (x > y ? 1 : 0), compare.output.getValues()[3], 0.001); - assertEquals("Compare partial", (x > y ? 1 : 0), compare.output.getValues()[4], 0.001); - assertEquals("Compare partial", 0.0, compare.output.getValues()[5], 0.001); - assertEquals("Compare partial", 0.0, compare.output.getValues()[6], 0.001); - assertEquals("Compare partial", 0.0, compare.output.getValues()[7], 0.001); - - } - - /** - * Unit test for Maximum.java - added by Lisa Tolentino 06/20/2009 - */ - public void testMaximum() { - Maximum max = new Maximum(); - max.setSynthesisEngine(synthesisEngine); - - double x = 33.99; - double y = 8.31; - max.inputA.setValueInternal(x); - max.inputB.setValueInternal(y); - - max.generate(); - - assertEquals("Maximum", (x > y ? x : y), max.output.getValue(), 0.001); - } - - public void testPartialMaximum() { - Maximum max = new Maximum(); - max.setSynthesisEngine(synthesisEngine); - - double x = 33.99; - double y = 8.31; - max.inputA.setValueInternal(x); - max.inputB.setValueInternal(y); - - // Only generate a few values in the middle. - // This is to test low latency feedback loops. - // Only generate values for 2,3,4 - max.generate(2, 5); - - assertEquals("Maximum partial", 0.0, max.output.getValues()[0], 0.001); - assertEquals("Maximum partial", 0.0, max.output.getValues()[1], 0.001); - assertEquals("Maximum partial", (x > y ? x : y), max.output.getValues()[2], 0.001); - assertEquals("Maximum partial", (x > y ? x : y), max.output.getValues()[3], 0.001); - assertEquals("Maximum partial", (x > y ? x : y), max.output.getValues()[4], 0.001); - assertEquals("Maximum partial", 0.0, max.output.getValues()[5], 0.001); - assertEquals("Maximum partial", 0.0, max.output.getValues()[6], 0.001); - assertEquals("Maximum partial", 0.0, max.output.getValues()[7], 0.001); - - } - - /** - * Unit test for Minimum.java - added by Lisa Tolentino 06/20/2009 - */ - public void testMinimum() { - Minimum min = new Minimum(); - min.setSynthesisEngine(synthesisEngine); - - double x = 33.99; - double y = 8.31; - min.inputA.setValueInternal(x); - min.inputB.setValueInternal(y); - - min.generate(); - - assertEquals("Minimum", (x < y ? x : y), min.output.getValue(), 0.001); - } - - public void testPartialMinimum() { - Minimum min = new Minimum(); - min.setSynthesisEngine(synthesisEngine); - - double x = 33.99; - double y = 8.31; - min.inputA.setValueInternal(x); - min.inputB.setValueInternal(y); - - // Only generate a few values in the middle. - // This is to test low latency feedback loops. - // Only generate values for 2,3,4 - min.generate(2, 5); - - assertEquals("Maximum partial", 0.0, min.output.getValues()[0], 0.001); - assertEquals("Maximum partial", 0.0, min.output.getValues()[1], 0.001); - assertEquals("Maximum partial", (x < y ? x : y), min.output.getValues()[2], 0.001); - assertEquals("Maximum partial", (x < y ? x : y), min.output.getValues()[3], 0.001); - assertEquals("Maximum partial", (x < y ? x : y), min.output.getValues()[4], 0.001); - assertEquals("Maximum partial", 0.0, min.output.getValues()[5], 0.001); - assertEquals("Maximum partial", 0.0, min.output.getValues()[6], 0.001); - assertEquals("Maximum partial", 0.0, min.output.getValues()[7], 0.001); - - } - - public void testPowerOfTwo() { - PowerOfTwo powerOfTwo = new PowerOfTwo(); - powerOfTwo.setSynthesisEngine(synthesisEngine); - final double smallValue = -1.5308084989341915E-17; - double values[] = { - 0.0, 1.3, 4.5, -0.5, -1.0, -2.8, smallValue, -smallValue, 1.0 - smallValue, - 1.0 + smallValue - }; - for (double in : values) { - powerOfTwo.input.setValueInternal(in); - powerOfTwo.generate(); - assertEquals("PowerOfTwo", Math.pow(2.0, in), powerOfTwo.output.getValue(), 0.001); - } - } - public void testPitchToFrequency() { - PitchToFrequency ugen = new PitchToFrequency(); - ugen.setSynthesisEngine(synthesisEngine); - final double smallValue = -1.5308084989341915E-17; - double values[] = { - 49.0, 49.5, 50.0 + smallValue, - 60.0 -smallValue, - 79.2, 12.9, 118.973 - }; - // Sanity check AudioMath - assertEquals("PitchToFrequency", 440.0, AudioMath.pitchToFrequency(69), 0.001); - assertEquals("PitchToFrequency", 660.0, AudioMath.pitchToFrequency(69+7.02), 0.1); - - for (double pitch : values) { - ugen.input.setValueInternal(pitch); - ugen.generate(); - assertEquals("PitchToFrequency", AudioMath.pitchToFrequency(pitch), - ugen.output.getValue(), 0.001); - } - } - -} diff --git a/tests/com/jsyn/unitgen/TestRamps.java b/tests/com/jsyn/unitgen/TestRamps.java deleted file mode 100644 index 83ebacf..0000000 --- a/tests/com/jsyn/unitgen/TestRamps.java +++ /dev/null @@ -1,196 +0,0 @@ -/* - * 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.unitgen; - -public class TestRamps extends NonRealTimeTestCase { - - public void viewContinuousRamp(double duration, double startValue, double targetValue) - throws InterruptedException { - ContinuousRamp ramp = new ContinuousRamp(); - synthesisEngine.add(ramp); - - ramp.current.set(startValue); - ramp.input.set(startValue); - ramp.time.set(duration); - - synthesisEngine.setRealTime(false); - synthesisEngine.start(); - ramp.start(); - synthesisEngine.sleepUntil(synthesisEngine.getCurrentTime() + 0.01); - ramp.input.set(targetValue); - - double time = synthesisEngine.getCurrentTime(); - int numLoops = 20; - double increment = duration / numLoops; - for (int i = 0; i < (numLoops + 1); i++) { - double value = ramp.output.getValue(); - System.out.printf("i = %d, t = %9.5f, value = %8.4f\n", i, time, value); - time += increment; - synthesisEngine.sleepUntil(time); - } - - synthesisEngine.stop(); - } - - public void checkContinuousRamp(double duration, double startValue, double targetValue) - throws InterruptedException { - ContinuousRamp ramp = new ContinuousRamp(); - synthesisEngine.add(ramp); - - ramp.current.set(startValue); - ramp.input.set(startValue); - ramp.time.set(duration); - - synthesisEngine.setRealTime(false); - synthesisEngine.start(); - ramp.start(); - synthesisEngine.sleepUntil(synthesisEngine.getCurrentTime() + 0.01); - assertEquals("start flat", ramp.input.getValue(), ramp.output.getValue()); - - ramp.input.set(targetValue); - double startTime = synthesisEngine.getCurrentTime(); - synthesisEngine.sleepUntil(startTime + (duration / 2)); - assertEquals("ramping up", (targetValue + startValue) / 2.0, ramp.output.getValue(), 0.01); - synthesisEngine.sleepUntil(startTime + duration); - assertEquals("ramping up", targetValue, ramp.output.getValue(), 0.01); - synthesisEngine.sleepUntil(startTime + duration + 0.1); - assertEquals("flat again", targetValue, ramp.output.getValue()); - - synthesisEngine.stop(); - } - - public void testContinuousRamp() throws InterruptedException { - viewContinuousRamp(4.0, 0.0, 1.0); - } - - public void testExponentialRamp() throws InterruptedException { - ExponentialRamp ramp = new ExponentialRamp(); - synthesisEngine.add(ramp); - - double duration = 0.3; - ramp.current.set(1.0); - ramp.input.set(1.0); - ramp.time.set(duration); - - synthesisEngine.start(); - ramp.start(); - synthesisEngine.sleepUntil(synthesisEngine.getCurrentTime() + 0.01); - assertEquals("start flat", ramp.input.getValue(), ramp.output.getValue()); - - ramp.input.set(8.0); - double startTime = synthesisEngine.getCurrentTime(); - synthesisEngine.sleepUntil(startTime + 0.1); - assertEquals("ramping up", 2.0, ramp.output.getValue(), 0.01); - synthesisEngine.sleepUntil(startTime + 0.2); - assertEquals("ramping up", 4.0, ramp.output.getValue(), 0.01); - synthesisEngine.sleepUntil(startTime + 0.3); - assertEquals("ramping up", 8.0, ramp.output.getValue(), 0.01); - synthesisEngine.sleepUntil(startTime + 0.4); - assertEquals("flat again", 8.0, ramp.output.getValue()); - } - - public void testLinearRamp() throws InterruptedException { - LinearRamp ramp = new LinearRamp(); - synthesisEngine.add(ramp); - - double duration = 0.4; - ramp.current.set(0.0); - ramp.input.set(0.0); - ramp.time.set(duration); - - synthesisEngine.start(); - ramp.start(); - synthesisEngine.sleepUntil(synthesisEngine.getCurrentTime() + 0.01); - assertEquals("start flat", ramp.input.getValue(), ramp.output.getValue()); - - ramp.input.set(8.0); - double startTime = synthesisEngine.getCurrentTime(); - synthesisEngine.sleepUntil(startTime + 0.1); - assertEquals("ramping up", 2.0, ramp.output.getValue(), 0.01); - synthesisEngine.sleepUntil(startTime + 0.2); - assertEquals("ramping up", 4.0, ramp.output.getValue(), 0.01); - synthesisEngine.sleepUntil(startTime + 0.3); - assertEquals("ramping up", 6.0, ramp.output.getValue(), 0.01); - synthesisEngine.sleepUntil(startTime + 0.4); - assertEquals("flat again", 8.0, ramp.output.getValue()); - } - - public void testExponentialRampConnected() throws InterruptedException { - ExponentialRamp ramp = new ExponentialRamp(); - PassThrough pass = new PassThrough(); - synthesisEngine.add(ramp); - synthesisEngine.add(pass); - - double duration = 0.3; - ramp.current.set(1.0); - pass.input.set(1.0); - ramp.time.set(duration); - - // Send value through a connected unit. - pass.input.set(1.0); - pass.output.connect(ramp.input); - - synthesisEngine.start(); - ramp.start(); - synthesisEngine.sleepUntil(synthesisEngine.getCurrentTime() + 0.01); - assertEquals("start flat", ramp.input.getValue(), ramp.output.getValue()); - - pass.input.set(8.0); - double startTime = synthesisEngine.getCurrentTime(); - synthesisEngine.sleepUntil(startTime + 0.1); - assertEquals("ramping up", 2.0, ramp.output.getValue(), 0.01); - synthesisEngine.sleepUntil(startTime + 0.2); - assertEquals("ramping up", 4.0, ramp.output.getValue(), 0.01); - synthesisEngine.sleepUntil(startTime + 0.3); - assertEquals("ramping up", 8.0, ramp.output.getValue(), 0.01); - synthesisEngine.sleepUntil(startTime + 0.4); - assertEquals("flat again", 8.0, ramp.output.getValue()); - } - - public void testLinearRampConnected() throws InterruptedException { - LinearRamp ramp = new LinearRamp(); - PassThrough pass = new PassThrough(); - synthesisEngine.add(ramp); - synthesisEngine.add(pass); - - double duration = 0.4; - ramp.current.set(0.0); - pass.input.set(0.0); - ramp.time.set(duration); - - // Send value through a connected unit. - pass.input.set(0.0); - pass.output.connect(ramp.input); - - synthesisEngine.start(); - ramp.start(); - synthesisEngine.sleepUntil(synthesisEngine.getCurrentTime() + 0.01); - assertEquals("start flat", ramp.input.getValue(), ramp.output.getValue()); - - pass.input.set(8.0); - double startTime = synthesisEngine.getCurrentTime(); - synthesisEngine.sleepUntil(startTime + 0.1); - assertEquals("ramping up", 2.0, ramp.output.getValue(), 0.01); - synthesisEngine.sleepUntil(startTime + 0.2); - assertEquals("ramping up", 4.0, ramp.output.getValue(), 0.01); - synthesisEngine.sleepUntil(startTime + 0.3); - assertEquals("ramping up", 6.0, ramp.output.getValue(), 0.01); - synthesisEngine.sleepUntil(startTime + 0.4); - assertEquals("flat again", 8.0, ramp.output.getValue()); - } - -} diff --git a/tests/com/jsyn/unitgen/TestUnitGate.java b/tests/com/jsyn/unitgen/TestUnitGate.java deleted file mode 100644 index 14129aa..0000000 --- a/tests/com/jsyn/unitgen/TestUnitGate.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * 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.unitgen; - -import junit.framework.TestCase; - -import com.jsyn.engine.SynthesisEngine; - -public class TestUnitGate extends TestCase { - - protected SynthesisEngine synthesisEngine; - protected double time; - - public void checkAutoDisable(LinearRamp ramp, UnitGate envelope) throws InterruptedException { - double tolerance = 0.01; - Add adder = new Add(); - synthesisEngine.add(adder); - - envelope.output.connect(adder.inputA); - if (ramp.getCircuit() != null) { - ramp.output.connect(adder.inputB); - } - - envelope.input.setAutoDisableEnabled(true); - envelope.setEnabled(false); - - // set up so ramp value should equal time - ramp.current.set(0.0); - ramp.input.set(1.0); - ramp.time.set(1.0); - - synthesisEngine.start(); - // pull from final adder - adder.start(); - - time = synthesisEngine.getCurrentTime(); - time += 0.1; - synthesisEngine.sleepUntil(time); - assertEquals("still idling", 0.0, envelope.output.getValue()); - assertEquals("ramp frozen at beginning", 0.0, ramp.output.getValue(), tolerance); - - // run multiple times to make sure we can retrigger the envelope. - for (int i = 0; i < 3; i++) { - double level = ramp.output.getValue(); - // Trigger the envelope using trigger() - envelope.input.on(); - time += 0.1; - level += 0.1; - synthesisEngine.sleepUntil(time); - assertEquals("ramp going up " + i, level, ramp.output.getValue(), tolerance); - assertTrue("enabled at peak", envelope.isEnabled()); - - envelope.input.off(); - time += 0.1; - level += 0.1; - synthesisEngine.sleepUntil(time); - assertEquals("ramp going up more " + i, level, ramp.output.getValue(), tolerance); - assertEquals("at bottom", 0.0, envelope.output.getValue(), 0.1); - - time += 0.2; - synthesisEngine.sleepUntil(time); - assertEquals("ramp frozen " + i, level, ramp.output.getValue(), tolerance); - } - } - -} diff --git a/tests/com/jsyn/util/DebugSampleLoader.java b/tests/com/jsyn/util/DebugSampleLoader.java deleted file mode 100644 index 23945b5..0000000 --- a/tests/com/jsyn/util/DebugSampleLoader.java +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright 2010 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.util; - -import java.io.File; -import java.io.IOException; - -import com.jsyn.JSyn; -import com.jsyn.Synthesizer; -import com.jsyn.data.FloatSample; -import com.jsyn.unitgen.LineOut; -import com.jsyn.unitgen.VariableRateDataReader; -import com.jsyn.unitgen.VariableRateMonoReader; -import com.jsyn.unitgen.VariableRateStereoReader; - -/** - * Play a sample from a WAV file using JSyn. - * - * @author Phil Burk (C) 2010 Mobileer Inc - */ -public class DebugSampleLoader { - private Synthesizer synth; - private VariableRateDataReader samplePlayer; - private LineOut lineOut; - - private void test() throws IOException { - // File sampleFile = new File("samples/cello_markers.wav"); - // File sampleFile = new File("samples/Piano_A440_PT.aif"); - File sampleFile = new File("samples/sine_400_loop_i16.wav"); - // File sampleFile = new File("samples/TwoDiffPitchedSines_F32_PT.wav"); - // File sampleFile = new File("samples/sine_400_u8.aif"); - // File sampleFile = new File("samples/sine_400_s8.aif"); - // File sampleFile = new File("samples/sine_400_ulaw.aif"); - // File sampleFile = new File("samples/sine_400_ulaw.wav"); - - // File sampleFile = new File("samples/aaClarinet.wav"); - // File sampleFile = new File("samples/sine_400_mono.wav"); - // File sampleFile = new File("samples/sine_200_300_i16.wav"); - // File sampleFile = new File("samples/sine_200_300_i24.wav"); - // File sampleFile = new File("samples/M1F1-int16-AFsp.wav"); - // File sampleFile = new File("samples/M1F1-int24-AFsp.wav"); - // File sampleFile = new File("samples/M1F1-float32-AFsp.wav"); - // File sampleFile = new File("samples/M1F1-int16WE-AFsp.wav"); - // File sampleFile = new File("samples/M1F1-int24WE-AFsp.wav"); - // File sampleFile = new File("samples/M1F1-float32WE-AFsp.wav"); - // File sampleFile = new File("samples/sine_200_300_i16.aif"); - // File sampleFile = new File("samples/sine_200_300_f32.wavex"); - // File sampleFile = new File("samples/Sine32bit.aif"); - // File sampleFile = new File("samples/Sine32bit.wav"); - // File sampleFile = new File("samples/smartCue.wav"); - - // URL sampleFile = new URL("http://www.softsynth.com/samples/Clarinet.wav"); - - synth = JSyn.createSynthesizer(); - - FloatSample sample; - try { - // Add an output mixer. - synth.add(lineOut = new LineOut()); - - // Load the sample and display its properties. - SampleLoader.setJavaSoundPreferred(false); - sample = SampleLoader.loadFloatSample(sampleFile); - System.out.println("Sample has: channels = " + sample.getChannelsPerFrame()); - System.out.println(" frames = " + sample.getNumFrames()); - System.out.println(" rate = " + sample.getFrameRate()); - System.out.println(" loopStart = " + sample.getSustainBegin()); - System.out.println(" loopEnd = " + sample.getSustainEnd()); - - if (sample.getChannelsPerFrame() == 1) { - synth.add(samplePlayer = new VariableRateMonoReader()); - samplePlayer.output.connect(0, lineOut.input, 0); - } else if (sample.getChannelsPerFrame() == 2) { - synth.add(samplePlayer = new VariableRateStereoReader()); - samplePlayer.output.connect(0, lineOut.input, 0); - samplePlayer.output.connect(1, lineOut.input, 1); - } else { - throw new RuntimeException("Can only play mono or stereo samples."); - } - - // Start synthesizer using default stereo output at 44100 Hz. - synth.start(); - - samplePlayer.rate.set(sample.getFrameRate()); - - // We only need to start the LineOut. It will pull data from the - // sample player. - lineOut.start(); - - // We can simply queue the entire file. - // Or if it has a loop we can play the loop for a while. - if (sample.getSustainBegin() < 0) { - System.out.println("queue the sample"); - samplePlayer.dataQueue.queue(sample); - } else { - System.out.println("queueOn the sample"); - samplePlayer.dataQueue.queueOn(sample); - synth.sleepFor(8.0); - System.out.println("queueOff the sample"); - samplePlayer.dataQueue.queueOff(sample); - } - - // Wait until the sample has finished playing. - do { - synth.sleepFor(1.0); - } while (samplePlayer.dataQueue.hasMore()); - - } catch (IOException e1) { - e1.printStackTrace(); - } catch (InterruptedException e) { - e.printStackTrace(); - } - // Stop everything. - synth.stop(); - } - - public static void main(String[] args) { - try { - new DebugSampleLoader().test(); - } catch (IOException e) { - e.printStackTrace(); - } - } -} diff --git a/tests/com/jsyn/util/TestFFT.java b/tests/com/jsyn/util/TestFFT.java deleted file mode 100644 index c79157d..0000000 --- a/tests/com/jsyn/util/TestFFT.java +++ /dev/null @@ -1,207 +0,0 @@ -/* - * 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.util; - -import junit.framework.TestCase; - -import com.softsynth.math.FourierMath; - -public class TestFFT extends TestCase { - - @Override - protected void setUp() throws Exception { - super.setUp(); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - } - - public void checkSingleSineDouble(int size, int bin) { - double[] ar = new double[size]; - double[] ai = new double[size]; - double[] magnitudes = new double[size]; - - double amplitude = 1.0; - addSineWave(size, bin, ar, amplitude); - - FourierMath.transform(1, size, ar, ai); - FourierMath.calculateMagnitudes(ar, ai, magnitudes); - - assertEquals("magnitude", 0.0, magnitudes[bin-1], 0.000001); - assertEquals("magnitude", amplitude, magnitudes[bin], 0.000001); - assertEquals("magnitude", 0.0, magnitudes[bin+1], 0.000001); - /* - for (int i = 0; i < magnitudes.length; i++) { - System.out.printf("%d = %9.7f\n", i, magnitudes[i]); - } -*/ - - } - - public void checkSingleSineFloat(int size, int bin) { - float[] ar = new float[size]; - float[] ai = new float[size]; - float[] magnitudes = new float[size]; - - double amplitude = 1.0; - addSineWave(size, bin, ar, amplitude); - - FourierMath.transform(1, size, ar, ai); - FourierMath.calculateMagnitudes(ar, ai, magnitudes); - - assertEquals("magnitude", 0.0f, magnitudes[bin-1], 0.000001); - assertEquals("magnitude", amplitude, magnitudes[bin], 0.000001); - assertEquals("magnitude", 0.0f, magnitudes[bin+1], 0.000001); -/* - for (int i = 0; i < magnitudes.length; i++) { - System.out.printf("%d = %9.7f\n", i, magnitudes[i]); - } -*/ - } - - public void checkMultipleSine(int size, int[] bins, double[] amplitudes) { - double[] ar = new double[size]; - double[] ai = new double[size]; - double[] magnitudes = new double[size]; - - for(int i = 0; i= 0)); - double rand = positiveInt * (1.0 / (1L << 31)); - assertTrue("not too low, " + rand, (rand >= 0.0)); - assertTrue("not too high, " + rand, (rand < 1.0)); - } - - public void testIntegerDistribution() { - int scaler = 100; - for (int i = 0; i < (bins.length * scaler); i++) { - int rand = pseudoRandom.nextRandomInteger(); - int positiveInt = rand & 0x7FFFFFFF; - assertTrue("masked random " + positiveInt, (positiveInt >= 0)); - int index = (rand >> (32 - BIN_SHIFTER)) & BIN_MASK; - bins[index] += 1; - } - checkDistribution(scaler); - } - - public void test01Distribution() { - int scaler = 100; - for (int i = 0; i < (bins.length * scaler); i++) { - double rand = pseudoRandom.random(); - assertTrue("not too low, #" + i + " = " + rand, (rand >= 0.0)); - assertTrue("not too high, #" + i + " = " + rand, (rand < 1.0)); - int index = (int) (rand * BIN_COUNT); - bins[index] += 1; - } - checkDistribution(scaler); - } - - private void checkDistribution(int scaler) { - // Generate running average that should stay near scaler - double average = scaler; - double coefficient = 0.9; - for (int i = 0; i < (bins.length); i++) { - average = (average * coefficient) + (bins[i] * (1.0 - coefficient)); - assertEquals("average at " + i, scaler, average, 0.2 * scaler); - } - } -} diff --git a/tests/com/jsyn/util/TestVoiceAllocator.java b/tests/com/jsyn/util/TestVoiceAllocator.java deleted file mode 100644 index fd5ba0b..0000000 --- a/tests/com/jsyn/util/TestVoiceAllocator.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * 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.util; - -import junit.framework.TestCase; - -import com.jsyn.instruments.SubtractiveSynthVoice; -import com.jsyn.unitgen.UnitVoice; - -public class TestVoiceAllocator extends TestCase { - VoiceAllocator allocator; - int max = 4; - private UnitVoice[] voices; - - @Override - protected void setUp() throws Exception { - super.setUp(); - - voices = new UnitVoice[max]; - for (int i = 0; i < max; i++) { - voices[i] = new SubtractiveSynthVoice(); - } - - allocator = new VoiceAllocator(voices); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - } - - public void testAllocation() { - assertEquals("get max", max, allocator.getVoiceCount()); - - int tag1 = 61; - int tag2 = 62; - int tag3 = 63; - int tag4 = 64; - int tag5 = 65; - int tag6 = 66; - UnitVoice voice1 = allocator.allocate(tag1); - assertTrue("voice should be non-null", (voice1 != null)); - - UnitVoice voice2 = allocator.allocate(tag2); - assertTrue("voice should be non-null", (voice2 != null)); - assertTrue("new voice ", (voice2 != voice1)); - - UnitVoice voice = allocator.allocate(tag1); - assertTrue("should be voice1 again ", (voice == voice1)); - - voice = allocator.allocate(tag2); - assertTrue("should be voice2 again ", (voice == voice2)); - - UnitVoice voice3 = allocator.allocate(tag3); - @SuppressWarnings("unused") - UnitVoice voice4 = allocator.allocate(tag4); - - UnitVoice voice5 = allocator.allocate(tag5); - assertTrue("ran out so get voice1 as oldest", (voice5 == voice1)); - - voice = allocator.allocate(tag2); - assertTrue("should be voice2 again ", (voice == voice2)); - - // Now voice 3 should be the oldest cuz voice 2 was touched. - UnitVoice voice6 = allocator.allocate(tag6); - assertTrue("ran out so get voice3 as oldest", (voice6 == voice3)); - } - - public void testOff() { - int tag1 = 61; - int tag2 = 62; - int tag3 = 63; - int tag4 = 64; - int tag5 = 65; - int tag6 = 66; - UnitVoice voice1 = allocator.allocate(tag1); - UnitVoice voice2 = allocator.allocate(tag2); - UnitVoice voice3 = allocator.allocate(tag3); - UnitVoice voice4 = allocator.allocate(tag4); - - assertTrue("voice 3 should start on", allocator.isOn(tag3)); - allocator.off(tag3); - assertEquals("voice 3 should now be off", false, allocator.isOn(tag3)); - - allocator.off(tag2); - - UnitVoice voice5 = allocator.allocate(tag5); - assertTrue("should get voice3 cuz off first", (voice5 == voice3)); - UnitVoice voice6 = allocator.allocate(tag6); - assertTrue("should get voice2 cuz off second", (voice6 == voice2)); - voice3 = allocator.allocate(tag3); - assertTrue("should get voice1 cuz on first", (voice3 == voice1)); - - voice1 = allocator.allocate(tag1); - assertTrue("should get voice4 cuz next up", (voice1 == voice4)); - } -} -- cgit v1.2.3