diff --git a/.travis.yml b/.travis.yml index 9185ded113a..d1806bf0990 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,10 +4,6 @@ dist: trusty sudo: required matrix: include: - - env: TARGET_OS=win32 - - env: TARGET_OS=win64 - - os: osx - osx_image: xcode8.2 - env: QT5=True - env: QT5=True TARGET_OS=win32 - env: QT5=True TARGET_OS=win64 diff --git a/.travis/linux..install.sh b/.travis/linux..install.sh index 7b591feb962..da31f6ce6a0 100644 --- a/.travis/linux..install.sh +++ b/.travis/linux..install.sh @@ -11,7 +11,7 @@ PACKAGES="$PACKAGES libjack0" if [ $QT5 ]; then PACKAGES="$PACKAGES qtbase5-dev qttools5-dev-tools qttools5-dev" else - PACKAGES="$PACKAGES libqt4-dev" + PACKAGES="$PACKAGES libqt4-dev libdrumstick-dev" fi sudo apt-get install -y $PACKAGES diff --git a/.travis/linux..script.sh b/.travis/linux..script.sh index f4ab59f6f81..8ef8c513fc6 100644 --- a/.travis/linux..script.sh +++ b/.travis/linux..script.sh @@ -1,3 +1,7 @@ #!/usr/bin/env bash +if [ $QT5 ];then + bash ${TRAVIS_BUILD_DIR}/cmake/linux/build_linux_drumstick.sh 1.1.0 +fi + cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -DUSE_WERROR=ON -DWANT_QT5=$QT5 .. diff --git a/.travis/linux.win32.script.sh b/.travis/linux.win32.script.sh index 3058f71d297..072916646d9 100644 --- a/.travis/linux.win32.script.sh +++ b/.travis/linux.win32.script.sh @@ -1,4 +1,12 @@ #!/usr/bin/env bash export CMAKE_OPTS="-DUSE_WERROR=ON" + +# Just a temporarily solution. +if [ $QT5 ];then + bash ${TRAVIS_BUILD_DIR}/cmake/nsis/build_mingw_drumstick.sh 1.1.0 32 +else + bash ${TRAVIS_BUILD_DIR}/cmake/nsis/build_mingw_drumstick.sh 0.5.0 32 +fi + ../cmake/build_mingw32.sh diff --git a/.travis/linux.win64.script.sh b/.travis/linux.win64.script.sh index e7321df6a60..41390bf02ba 100644 --- a/.travis/linux.win64.script.sh +++ b/.travis/linux.win64.script.sh @@ -1,4 +1,11 @@ #!/usr/bin/env bash export CMAKE_OPTS="-DUSE_WERROR=ON" + + +if [ $QT5 ];then + bash ${TRAVIS_BUILD_DIR}/cmake/nsis/build_mingw_drumstick.sh 1.1.0 64 +else + bash ${TRAVIS_BUILD_DIR}/cmake/nsis/build_mingw_drumstick.sh 0.5.0 64 +fi ../cmake/build_mingw64.sh diff --git a/.travis/osx..script.sh b/.travis/osx..script.sh index f2d611e64d2..348619f3e64 100644 --- a/.travis/osx..script.sh +++ b/.travis/osx..script.sh @@ -5,4 +5,11 @@ if [ $QT5 ]; then export CMAKE_PREFIX_PATH="$(brew --prefix qt55)" fi +if [ $QT5 ];then + bash ${TRAVIS_BUILD_DIR}/cmake/apple/build_osx_drumstick.sh 1.1.0 +else + + bash ${TRAVIS_BUILD_DIR}/cmake/apple/build_osx_drumstick.sh 0.5.0 +fi + cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -DWANT_QT5=$QT5 -DUSE_WERROR=OFF .. diff --git a/cmake/apple/build_osx_drumstick.sh b/cmake/apple/build_osx_drumstick.sh new file mode 100644 index 00000000000..0439000d85f --- /dev/null +++ b/cmake/apple/build_osx_drumstick.sh @@ -0,0 +1,23 @@ +drumstick_ver=$1 + +cd /tmp + +wget https://master.dl.sourceforge.net/project/drumstick/${drumstick_ver}/drumstick-${drumstick_ver}.tar.gz +tar xf drumstick-${drumstick_ver}.tar.gz +cd drumstick-${drumstick_ver} + +# We just need library +sed -i "" "/ADD_SUBDIRECTORY(utils)/d" CMakeLists.txt +sed -i "" "/add_subdirectory(utils)/d" CMakeLists.txt + +mkdir -p build && cd build + +if [ $QT5 ];then + STATIC_OPTS=ON +else + STATIC_OPTS=OFF +fi + +cmake ../ -DUSE_WERROR=OFF -DLIB_SUFFIX='' -DSTATIC_DRUMSTICK=$STATIC_OPTS +make && sudo make install + diff --git a/cmake/linux/build_linux_drumstick.sh b/cmake/linux/build_linux_drumstick.sh new file mode 100644 index 00000000000..b472ba6e6eb --- /dev/null +++ b/cmake/linux/build_linux_drumstick.sh @@ -0,0 +1,17 @@ +drumstick_ver=$1 + +cd /tmp + +wget https://master.dl.sourceforge.net/project/drumstick/${drumstick_ver}/drumstick-${drumstick_ver}.tar.gz +tar xf drumstick-${drumstick_ver}.tar.gz +cd drumstick-${drumstick_ver} + +# We just need library +sed -i "/ADD_SUBDIRECTORY(utils)/d" CMakeLists.txt +sed -i "/add_subdirectory(utils)/d" CMakeLists.txt + +mkdir -p build && cd build + +cmake ../ -DUSE_WERROR=OFF -DLIB_SUFFIX='' -DCMAKE_INSTALL_PREFIX=/usr +make && sudo make install + diff --git a/cmake/nsis/build_mingw_drumstick.sh b/cmake/nsis/build_mingw_drumstick.sh new file mode 100644 index 00000000000..d4d3d30be94 --- /dev/null +++ b/cmake/nsis/build_mingw_drumstick.sh @@ -0,0 +1,54 @@ +drumstick_ver=$1 + +cd /tmp + +wget https://master.dl.sourceforge.net/project/drumstick/${drumstick_ver}/drumstick-${drumstick_ver}.tar.gz +tar xf drumstick-${drumstick_ver}.tar.gz +cd drumstick-${drumstick_ver} + +# We just need library, for utils depends on Qt5Svg +# But no mingw*-x-qt5svg +sed -i "/ADD_SUBDIRECTORY(utils)/d" CMakeLists.txt +sed -i "/add_subdirectory(utils)/d" CMakeLists.txt + +mkdir -p build && cd build + +case $2 in + 32) + export MINGW=/opt/mingw32 + export PROCESSOR=i686 + ;; + 64) + export MINGW=/opt/mingw64 + export PROCESSOR=x86_64 + ;; + *) + ;; +esac + +export PKG_CONFIG_PATH=${MINGW}/lib/pkgconfig +export MINGW_TOOL_PREFIX=${MINGW}/bin/${PROCESSOR}-w64-mingw32- + +export CMAKE_OPTS="-DUSE_WERROR=ON + -DLIB_SUFFIX='' + -DCMAKE_INSTALL_PREFIX=$MINGW + -DCMAKE_PREFIX_PATH=$MINGW + -DMINGW_PREFIX=$MINGW + -DCMAKE_SYSTEM_PROCESSOR=$PROCESSOR + -DCMAKE_SYSTEM_NAME=Windows + -DCMAKE_SYSTEM_VERSION=1 + -DCMAKE_FIND_ROOT_PATH=$MINGW + -DCMAKE_C_COMPILER=${MINGW_TOOL_PREFIX}gcc + -DCMAKE_CXX_COMPILER=${MINGW_TOOL_PREFIX}g++ + -DCMAKE_RC_COMPILER=${MINGW_TOOL_PREFIX}gcc + -DSTRIP=${MINGW_TOOL_PREFIX}strip + -DWINDRES=${MINGW_TOOL_PREFIX}windres + -DPKG_CONFIG_EXECUTABLE=${MINGW_TOOL_PREFIX}pkgconfig + -DQT_BINARY_DIR=$MINGW/bin + -DQT_QMAKE_EXECUTABLE=$MINGW/bin/qmake" + +export PATH=$PATH:$MINGW/bin + +cmake ../ $CMAKE_OPTS +make && sudo make install + diff --git a/data/locale/en.ts b/data/locale/en.ts index 400ed17c996..e60a19c513d 100644 --- a/data/locale/en.ts +++ b/data/locale/en.ts @@ -4,91 +4,112 @@ AboutDialog + About LMMS - Version %1 (%2/%3, Qt %4, %5) + + LMMS - About + + Version %1 (%2/%3, Qt %4, %5) - LMMS - easy music production for everyone + + About - Authors + + LMMS - easy music production for everyone - Translation + + Copyright © %1 - Current language not translated (or native English). - -If you're interested in translating LMMS in another language or want to improve existing translations, you're welcome to help us! Simply contact the maintainer! + + <html><head/><body><p><a href="http://lmms.io"><span style=" text-decoration: underline; color:#0000ff;">http://lmms.io</span></a></p></body></html> - License + + Authors - LMMS + + Involved - <html><head/><body><p><a href="http://lmms.io"><span style=" text-decoration: underline; color:#0000ff;">http://lmms.io</span></a></p></body></html> + + Contributors ordered by number of commits: - Involved + + Translation - Contributors ordered by number of commits: + + Current language not translated (or native English). + +If you're interested in translating LMMS in another language or want to improve existing translations, you're welcome to help us! Simply contact the maintainer! - Copyright © %1 + + License AmplifierControlDialog + VOL + Volume: + PAN + Panning: + LEFT + Left gain: + RIGHT + Right gain: @@ -96,18 +117,22 @@ If you're interested in translating LMMS in another language or want to imp AmplifierControls + Volume + Panning + Left gain + Right gain @@ -115,10 +140,12 @@ If you're interested in translating LMMS in another language or want to imp AudioAlsaSetupWidget + DEVICE + CHANNELS @@ -126,78 +153,98 @@ If you're interested in translating LMMS in another language or want to imp AudioFileProcessorView + Open other sample + Click here, if you want to open another audio-file. A dialog will appear where you can select your file. Settings like looping-mode, start and end-points, amplify-value, and so on are not reset. So, it may not sound like the original sample. + Reverse sample + If you enable this button, the whole sample is reversed. This is useful for cool effects, e.g. a reversed crash. - Amplify: + + Disable loop - With this knob you can set the amplify ratio. When you set a value of 100% your sample isn't changed. Otherwise it will be amplified up or down (your actual sample-file isn't touched!) + + This button disables looping. The sample plays only once from start to end. - Startpoint: + + + Enable loop - Endpoint: + + This button enables forwards-looping. The sample loops between the end point and the loop point. - Continue sample playback across notes + + This button enables ping-pong-looping. The sample loops backwards and forwards between the end point and the loop point. - Enabling this option makes the sample continue playing across different notes - if you change pitch, or the note length stops before the end of the sample, then the next note played will continue where it left off. To reset the playback to the start of the sample, insert a note at the bottom of the keyboard (< 20 Hz) + + Continue sample playback across notes - Disable loop + + Enabling this option makes the sample continue playing across different notes - if you change pitch, or the note length stops before the end of the sample, then the next note played will continue where it left off. To reset the playback to the start of the sample, insert a note at the bottom of the keyboard (< 20 Hz) - This button disables looping. The sample plays only once from start to end. + + Amplify: - Enable loop + + With this knob you can set the amplify ratio. When you set a value of 100% your sample isn't changed. Otherwise it will be amplified up or down (your actual sample-file isn't touched!) - This button enables forwards-looping. The sample loops between the end point and the loop point. + + Startpoint: - This button enables ping-pong-looping. The sample loops backwards and forwards between the end point and the loop point. + + With this knob you can set the point where AudioFileProcessor should begin playing your sample. - With this knob you can set the point where AudioFileProcessor should begin playing your sample. + + Endpoint: + With this knob you can set the point where AudioFileProcessor should stop playing your sample. + Loopback point: + With this knob you can set the point where the loop starts. @@ -205,6 +252,7 @@ If you're interested in translating LMMS in another language or want to imp AudioFileProcessorWaveView + Sample length: @@ -212,26 +260,35 @@ If you're interested in translating LMMS in another language or want to imp AudioJack + JACK client restarted + LMMS was kicked by JACK for some reason. Therefore the JACK backend of LMMS has been restarted. You will have to make manual connections again. + JACK server down + The JACK server seems to have been shutdown and starting a new instance failed. Therefore LMMS is unable to proceed. You should save your project and restart JACK and LMMS. + + + AudioJack::setupWidget + CLIENT-NAME + CHANNELS @@ -239,10 +296,12 @@ If you're interested in translating LMMS in another language or want to imp AudioOss::setupWidget + DEVICE + CHANNELS @@ -250,10 +309,12 @@ If you're interested in translating LMMS in another language or want to imp AudioPortAudio::setupWidget + BACKEND + DEVICE @@ -261,10 +322,12 @@ If you're interested in translating LMMS in another language or want to imp AudioPulseAudio::setupWidget + DEVICE + CHANNELS @@ -272,6 +335,7 @@ If you're interested in translating LMMS in another language or want to imp AudioSdl::setupWidget + DEVICE @@ -279,10 +343,12 @@ If you're interested in translating LMMS in another language or want to imp AudioSndio::setupWidget + DEVICE + CHANNELS @@ -290,10 +356,12 @@ If you're interested in translating LMMS in another language or want to imp AudioSoundIo::setupWidget + BACKEND + DEVICE @@ -301,61 +369,75 @@ If you're interested in translating LMMS in another language or want to imp AutomatableModel + &Reset (%1%2) + &Copy value (%1%2) + &Paste value (%1%2) + Edit song-global automation - Connected to %1 + + Remove song-global automation - Connected to controller + + Remove all linked controls - Edit connection... + + Connected to %1 - Remove connection + + Connected to controller - Connect to controller... + + Edit connection... - Remove song-global automation + + Remove connection - Remove all linked controls + + Connect to controller... AutomationEditor + Please open an automation pattern with the context menu of a control! + Values copied + All selected values were copied to the clipboard. @@ -363,142 +445,179 @@ If you're interested in translating LMMS in another language or want to imp AutomationEditorWindow + Play/pause current pattern (Space) + Click here if you want to play the current pattern. This is useful while editing it. The pattern is automatically looped when the end is reached. + Stop playing of current pattern (Space) + Click here if you want to stop playing of the current pattern. + + Edit actions + + + + Draw mode (Shift+D) + Erase mode (Shift+E) + Flip vertically + Flip horizontally + Click here and the pattern will be inverted.The points are flipped in the y direction. + Click here and the pattern will be reversed. The points are flipped in the x direction. + Click here and draw-mode will be activated. In this mode you can add and move single values. This is the default mode which is used most of the time. You can also press 'Shift+D' on your keyboard to activate this mode. + Click here and erase-mode will be activated. In this mode you can erase single values. You can also press 'Shift+E' on your keyboard to activate this mode. + + Interpolation controls + + + + Discrete progression + Linear progression + Cubic Hermite progression + Tension value for spline + A higher tension value may make a smoother curve but overshoot some values. A low tension value will cause the slope of the curve to level off at each control point. + Click here to choose discrete progressions for this automation pattern. The value of the connected object will remain constant between control points and be set immediately to the new value when each control point is reached. + Click here to choose linear progressions for this automation pattern. The value of the connected object will change at a steady rate over time between control points to reach the correct value at each control point without a sudden change. + Click here to choose cubic hermite progressions for this automation pattern. The value of the connected object will change in a smooth curve and ease in to the peaks and valleys. + + Tension: + + + + Cut selected values (%1+X) + Copy selected values (%1+C) + Paste values from clipboard (%1+V) + Click here and selected values will be cut into the clipboard. You can paste them anywhere in any pattern by clicking on the paste button. + Click here and selected values will be copied into the clipboard. You can paste them anywhere in any pattern by clicking on the paste button. + Click here and the values from the clipboard will be pasted at the first visible measure. - Tension: - - - - Automation Editor - no pattern - - - - Automation Editor - %1 - - - - Edit actions + + Timeline controls - Interpolation controls + + Zoom controls - Timeline controls + + Quantization controls - Zoom controls + + + Automation Editor - no pattern - Quantization controls + + + Automation Editor - %1 + Model is already connected to this pattern. @@ -506,6 +625,7 @@ If you're interested in translating LMMS in another language or want to imp AutomationPattern + Drag a control while pressing <%1> @@ -513,46 +633,57 @@ If you're interested in translating LMMS in another language or want to imp AutomationPatternView + double-click to open this pattern in automation editor + Open in Automation editor + Clear + Reset name + Change name - %1 Connections + + Set/clear record - Disconnect "%1" + + Flip Vertically (Visible) - Set/clear record + + Flip Horizontally (Visible) - Flip Vertically (Visible) + + %1 Connections - Flip Horizontally (Visible) + + Disconnect "%1" + Model is already connected to this pattern. @@ -560,6 +691,7 @@ If you're interested in translating LMMS in another language or want to imp AutomationTrack + Automation track @@ -567,77 +699,95 @@ If you're interested in translating LMMS in another language or want to imp BBEditor + Beat+Bassline Editor + Play/pause current beat/bassline (Space) + Stop playback of current beat/bassline (Space) + Click here to play the current beat/bassline. The beat/bassline is automatically looped when its end is reached. + Click here to stop playing of current beat/bassline. - Add beat/bassline + + Beat selector - Add automation-track + + Track and step actions - Remove steps + + Add beat/bassline - Add steps + + Add sample-track - Beat selector + + Add automation-track - Track and step actions + + Remove steps - Clone Steps + + Add steps - Add sample-track + + Clone Steps BBTCOView + Open in Beat+Bassline-Editor + Reset name + Change name + Change color + Reset color to default @@ -645,10 +795,12 @@ If you're interested in translating LMMS in another language or want to imp BBTrack + Beat/Bassline %1 + Clone of %1 @@ -656,26 +808,32 @@ If you're interested in translating LMMS in another language or want to imp BassBoosterControlDialog + FREQ + Frequency: + GAIN + Gain: + RATIO + Ratio: @@ -683,14 +841,17 @@ If you're interested in translating LMMS in another language or want to imp BassBoosterControls + Frequency + Gain + Ratio @@ -698,82 +859,104 @@ If you're interested in translating LMMS in another language or want to imp BitcrushControlDialog + IN + OUT + + GAIN + Input Gain: + NOIS + Input Noise: + Output Gain: + CLIP + Output Clip: + + Rate + Rate Enabled + Enable samplerate-crushing + Depth + Depth Enabled + Enable bitdepth-crushing + Sample rate: + STD + Stereo difference: + Levels + Levels: @@ -781,10 +964,12 @@ If you're interested in translating LMMS in another language or want to imp CaptionMenu + &Help + Help (not available) @@ -792,10 +977,12 @@ If you're interested in translating LMMS in another language or want to imp CarlaInstrumentView + Show GUI + Click here to show or hide the graphical user interface (GUI) of Carla. @@ -803,6 +990,7 @@ If you're interested in translating LMMS in another language or want to imp Controller + Controller %1 @@ -810,58 +998,73 @@ If you're interested in translating LMMS in another language or want to imp ControllerConnectionDialog + Connection Settings + MIDI CONTROLLER + Input channel + CHANNEL + Input controller + CONTROLLER + + Auto Detect + MIDI-devices to receive MIDI-events from + USER CONTROLLER + MAPPING FUNCTION + OK + Cancel + LMMS + Cycle Detected. @@ -869,18 +1072,22 @@ If you're interested in translating LMMS in another language or want to imp ControllerRackView + Controller Rack + Add + Confirm Delete + Confirm delete? There are existing connection(s) associated with this controller. There is no way to undo. @@ -888,93 +1095,115 @@ If you're interested in translating LMMS in another language or want to imp ControllerView + Controls + Controllers are able to automate the value of a knob, slider, and other controls. + Rename controller + Enter the new name for this controller - &Remove this controller + + LFO - Re&name this controller + + &Remove this controller - LFO + + Re&name this controller CrossoverEQControlDialog + Band 1/2 Crossover: + Band 2/3 Crossover: + Band 3/4 Crossover: + Band 1 Gain: + Band 2 Gain: + Band 3 Gain: + Band 4 Gain: + Band 1 Mute + Mute Band 1 + Band 2 Mute + Mute Band 2 + Band 3 Mute + Mute Band 3 + Band 4 Mute + Mute Band 4 @@ -982,22 +1211,27 @@ If you're interested in translating LMMS in another language or want to imp DelayControls + Delay Samples + Feedback + Lfo Frequency + Lfo Amount + Output gain @@ -1005,38 +1239,48 @@ If you're interested in translating LMMS in another language or want to imp DelayControlsDialog + Delay - Lfo Amt - - - + Delay Time + Regen + Feedback Amount + Rate + + Lfo + + Lfo Amt + + + + Out Gain + Gain @@ -1044,185 +1288,258 @@ If you're interested in translating LMMS in another language or want to imp DualFilterControlDialog - Filter 1 enabled + + + FREQ - Filter 2 enabled + + + Cutoff frequency - Click to enable/disable Filter 1 + + + RESO - Click to enable/disable Filter 2 + + + Resonance - FREQ + + + GAIN - Cutoff frequency + + + Gain - RESO + + MIX - Resonance + + Mix - GAIN + + Filter 1 enabled - Gain + + Filter 2 enabled - MIX + + Click to enable/disable Filter 1 - Mix + + Click to enable/disable Filter 2 DualFilterControls + Filter 1 enabled + Filter 1 type + Cutoff 1 frequency + Q/Resonance 1 + Gain 1 + Mix + Filter 2 enabled + Filter 2 type + Cutoff 2 frequency + Q/Resonance 2 + Gain 2 + + LowPass + + HiPass + + BandPass csg + + BandPass czpg + + Notch + + Allpass + + Moog + + 2x LowPass + + RC LowPass 12dB + + RC BandPass 12dB + + RC HighPass 12dB + + RC LowPass 24dB + + RC BandPass 24dB + + RC HighPass 24dB + + Vocal Formant Filter + + 2x Moog + + SV LowPass + + SV BandPass + + SV HighPass + + SV Notch + + Fast Formant + + Tripole @@ -1230,41 +1547,50 @@ If you're interested in translating LMMS in another language or want to imp Editor + + Transport controls + + + + Play (Space) + Stop (Space) + Record + Record while playing - - Transport controls - - Effect + Effect enabled + Wet/Dry mix + Gate + Decay @@ -1272,6 +1598,7 @@ If you're interested in translating LMMS in another language or want to imp EffectChain + Effects enabled @@ -1279,10 +1606,12 @@ If you're interested in translating LMMS in another language or want to imp EffectRackView + EFFECTS CHAIN + Add effect @@ -1290,22 +1619,28 @@ If you're interested in translating LMMS in another language or want to imp EffectSelectDialog + Add effect + + Name + Type + Description + Author @@ -1313,54 +1648,67 @@ If you're interested in translating LMMS in another language or want to imp EffectView + Toggles the effect on or off. + On/Off + W/D + Wet Level: + The Wet/Dry knob sets the ratio between the input signal and the effect signal that forms the output. + DECAY + Time: + The Decay knob controls how many buffers of silence must pass before the plugin stops processing. Smaller values will reduce the CPU overhead but run the risk of clipping the tail on delay and reverb effects. + GATE + Gate: + The Gate knob controls the signal level that is considered to be 'silence' while deciding when to stop processing signals. + Controls + Effect plugins function as a chained series of effects where the signal will be processed from top to bottom. The On/Off switch allows you to bypass a given plugin at any point in time. @@ -1377,14 +1725,17 @@ Right clicking will bring up a context menu where you can change the order in wh + Move &up + Move &down + &Remove this plugin @@ -1392,58 +1743,72 @@ Right clicking will bring up a context menu where you can change the order in wh EnvelopeAndLfoParameters + Predelay + Attack + Hold + Decay + Sustain + Release + Modulation + LFO Predelay + LFO Attack + LFO speed + LFO Modulation + LFO Wave Shape + Freq x 100 + Modulate Env-Amount @@ -1451,349 +1816,439 @@ Right clicking will bring up a context menu where you can change the order in wh EnvelopeAndLfoView + + DEL + Predelay: + Use this knob for setting predelay of the current envelope. The bigger this value the longer the time before start of actual envelope. + + ATT + Attack: + Use this knob for setting attack-time of the current envelope. The bigger this value the longer the envelope needs to increase to attack-level. Choose a small value for instruments like pianos and a big value for strings. + HOLD + Hold: + Use this knob for setting hold-time of the current envelope. The bigger this value the longer the envelope holds attack-level before it begins to decrease to sustain-level. + DEC + Decay: + Use this knob for setting decay-time of the current envelope. The bigger this value the longer the envelope needs to decrease from attack-level to sustain-level. Choose a small value for instruments like pianos. + SUST + Sustain: + Use this knob for setting sustain-level of the current envelope. The bigger this value the higher the level on which the envelope stays before going down to zero. + REL + Release: + Use this knob for setting release-time of the current envelope. The bigger this value the longer the envelope needs to decrease from sustain-level to zero. Choose a big value for soft instruments like strings. + + AMT + + Modulation amount: + Use this knob for setting modulation amount of the current envelope. The bigger this value the more the according size (e.g. volume or cutoff-frequency) will be influenced by this envelope. + LFO predelay: + Use this knob for setting predelay-time of the current LFO. The bigger this value the the time until the LFO starts to oscillate. + LFO- attack: + Use this knob for setting attack-time of the current LFO. The bigger this value the longer the LFO needs to increase its amplitude to maximum. + SPD + LFO speed: + Use this knob for setting speed of the current LFO. The bigger this value the faster the LFO oscillates and the faster will be your effect. + Use this knob for setting modulation amount of the current LFO. The bigger this value the more the selected size (e.g. volume or cutoff-frequency) will be influenced by this LFO. + Click here for a sine-wave. + Click here for a triangle-wave. + Click here for a saw-wave for current. + Click here for a square-wave. + Click here for a user-defined wave. Afterwards, drag an according sample-file onto the LFO graph. + + Click here for random wave. + + + + FREQ x 100 + Click here if the frequency of this LFO should be multiplied by 100. + multiply LFO-frequency by 100 + MODULATE ENV-AMOUNT + Click here to make the envelope-amount controlled by this LFO. + control envelope-amount by this LFO + ms/LFO: + Hint + Drag a sample from somewhere and drop it in this window. - - Click here for random wave. - - EqControls + Input gain + Output gain + Low shelf gain + Peak 1 gain + Peak 2 gain + Peak 3 gain + Peak 4 gain + High Shelf gain + HP res + Low Shelf res + Peak 1 BW + Peak 2 BW + Peak 3 BW + Peak 4 BW + High Shelf res + LP res + HP freq + Low Shelf freq + Peak 1 freq + Peak 2 freq + Peak 3 freq + Peak 4 freq + High shelf freq + LP freq + HP active + Low shelf active + Peak 1 active + Peak 2 active + Peak 3 active + Peak 4 active + High shelf active + LP active + LP 12 + LP 24 + LP 48 + HP 12 + HP 24 + HP 48 + low pass type + high pass type + Analyse IN + Analyse OUT @@ -1801,82 +2256,105 @@ Right clicking will bring up a context menu where you can change the order in wh EqControlsDialog + HP + Low Shelf + Peak 1 + Peak 2 + Peak 3 + Peak 4 + High Shelf + LP + In Gain + + + Gain + Out Gain + Bandwidth: + + Octave + + + + Resonance : + Frequency: + lp grp + hp grp - Octave - - - + Frequency + + Resonance + Bandwidth @@ -1884,14 +2362,18 @@ Right clicking will bring up a context menu where you can change the order in wh EqHandle + Reso: + BW: + + Freq: @@ -1899,167 +2381,208 @@ Right clicking will bring up a context menu where you can change the order in wh ExportProjectDialog + Export project + Output + File format: + Samplerate: + 44100 Hz + 48000 Hz + 88200 Hz + 96000 Hz + 192000 Hz + Bitrate: + 64 KBit/s + 128 KBit/s + 160 KBit/s + 192 KBit/s + 256 KBit/s + 320 KBit/s + Depth: + 16 Bit Integer + 32 Bit Float + Please note that not all of the parameters above apply for all file formats. + Quality settings + Interpolation: + Zero Order Hold + Sinc Fastest + Sinc Medium (recommended) + Sinc Best (very slow!) + Oversampling (use with care!): + 1x (None) + 2x + 4x + 8x - Start + + Export as loop (remove end silence) - Cancel + + Export between loop markers - Export as loop (remove end silence) + + Start - Export between loop markers + + Cancel + Could not open file + Could not open file %1 for writing. Please make sure you have write-permission to the file and the directory containing the file and try again! + Export project to %1 + Error + Error while determining file-encoder device. Please try to choose a different output format. + Rendering: %1% @@ -2067,6 +2590,8 @@ Please make sure you have write-permission to the file and the directory contain Fader + + Please enter a new value between %1 and %2: @@ -2074,6 +2599,7 @@ Please make sure you have write-permission to the file and the directory contain FileBrowser + Browser @@ -2081,65 +2607,80 @@ Please make sure you have write-permission to the file and the directory contain FileBrowserTreeWidget + Send to active instrument-track - Open in new instrument-track/B+B Editor - - - - Loading sample + + Open in new instrument-track/Song Editor - Please wait, loading sample for preview... + + Open in new instrument-track/B+B Editor - --- Factory files --- + + Loading sample - Open in new instrument-track/Song Editor + + Please wait, loading sample for preview... + Error + does not appear to be a valid + file + + + --- Factory files --- + + FlangerControls + Delay Samples + Lfo Frequency + Seconds + Regen + Noise + Invert @@ -2147,46 +2688,57 @@ Please make sure you have write-permission to the file and the directory contain FlangerControlsDialog - Delay Time: - - - - Feedback Amount: - - - - White Noise Amount: + + DELAY - DELAY + + Delay Time: + RATE + Rate: + AMNT + Amount: + FDBK + + Feedback Amount: + + + + NOISE + + White Noise Amount: + + + + Invert @@ -2194,10 +2746,12 @@ Please make sure you have write-permission to the file and the directory contain FxLine + Channel send amount + The FX channel receives input from one or more instrument tracks. It in turn can be routed to multiple other FX channels. LMMS automatically takes care of preventing infinite loops for you and doesn't allow making a connection that would result in an infinite loop. @@ -2208,22 +2762,27 @@ You can remove and move FX channels in the context menu, which is accessed by ri + Move &left + Move &right + Rename &channel + R&emove channel + Remove &unused channels @@ -2231,10 +2790,14 @@ You can remove and move FX channels in the context menu, which is accessed by ri FxMixer + Master + + + FX %1 @@ -2242,26 +2805,35 @@ You can remove and move FX channels in the context menu, which is accessed by ri FxMixerView + FX-Mixer + + + FxMixerView::FxChannelView + FX Fader %1 + Mute + Mute this FX channel + Solo + Solo FX channel @@ -2269,6 +2841,8 @@ You can remove and move FX channels in the context menu, which is accessed by ri FxRoute + + Amount to send from channel %1 to channel %2 @@ -2276,14 +2850,17 @@ You can remove and move FX channels in the context menu, which is accessed by ri GigInstrument + Bank + Patch + Gain @@ -2291,46 +2868,58 @@ You can remove and move FX channels in the context menu, which is accessed by ri GigInstrumentView + Open other GIG file + Click here to open another GIG file + Choose the patch + Click here to change which patch of the GIG file to use + + Change which instrument of the GIG file is being played + Which GIG file is currently being used + Which patch of the GIG file is currently being used + Gain + Factor to multiply samples by + Open GIG file + GIG Files (*.gig) @@ -2338,42 +2927,52 @@ You can remove and move FX channels in the context menu, which is accessed by ri GuiApplication + Working directory + The LMMS working directory %1 does not exist. Create it now? You can change the directory later via Edit -> Settings. + Preparing UI + Preparing song editor + Preparing mixer + Preparing controller rack + Preparing project notes + Preparing beat/bassline editor + Preparing piano roll + Preparing automation editor @@ -2381,650 +2980,814 @@ You can remove and move FX channels in the context menu, which is accessed by ri InstrumentFunctionArpeggio + Arpeggio + Arpeggio type + Arpeggio range + + Cycle steps + + + + + Skip rate + + + + + Miss rate + + + + Arpeggio time + Arpeggio gate + Arpeggio direction + Arpeggio mode + Up + Down + Up and down + + Down and up + + + + Random + Free + Sort + Sync + + + InstrumentFunctionArpeggioView - Down and up - - - - Skip rate - - - - Miss rate - - - - Cycle steps - - - - - InstrumentFunctionArpeggioView - + ARPEGGIO + An arpeggio is a method playing (especially plucked) instruments, which makes the music much livelier. The strings of such instruments (e.g. harps) are plucked like chords. The only difference is that this is done in a sequential order, so the notes are not played at the same time. Typical arpeggios are major or minor triads, but there are a lot of other possible chords, you can select. + RANGE + Arpeggio range: + octave(s) + Use this knob for setting the arpeggio range in octaves. The selected arpeggio will be played within specified number of octaves. - TIME + + CYCLE - Arpeggio time: + + Cycle notes: - ms + + note(s) - Use this knob for setting the arpeggio time in milliseconds. The arpeggio time specifies how long each arpeggio-tone should be played. + + Jumps over n steps in the arpeggio and cycles around if we're over the note range. If the total note range is evenly divisible by the number of steps jumped over you will get stuck in a shorter arpeggio or even on one note. - GATE + + SKIP - Arpeggio gate: + + Skip rate: + + + % - Use this knob for setting the arpeggio gate. The arpeggio gate specifies the percent of a whole arpeggio-tone that should be played. With this you can make cool staccato arpeggios. + + The skip function will make the arpeggiator pause one step randomly. From its start in full counter clockwise position and no effect it will gradually progress to full amnesia at maximum setting. - Chord: + + MISS - Direction: + + Miss rate: - Mode: + + The miss function will make the arpeggiator miss the intended note. - SKIP + + TIME - Skip rate: + + Arpeggio time: - The skip function will make the arpeggiator pause one step randomly. From its start in full counter clockwise position and no effect it will gradually progress to full amnesia at maximum setting. + + ms - MISS + + Use this knob for setting the arpeggio time in milliseconds. The arpeggio time specifies how long each arpeggio-tone should be played. - Miss rate: + + GATE - The miss function will make the arpeggiator miss the intended note. + + Arpeggio gate: - CYCLE + + Use this knob for setting the arpeggio gate. The arpeggio gate specifies the percent of a whole arpeggio-tone that should be played. With this you can make cool staccato arpeggios. - Cycle notes: + + Chord: - note(s) + + Direction: - Jumps over n steps in the arpeggio and cycles around if we're over the note range. If the total note range is evenly divisible by the number of steps jumped over you will get stuck in a shorter arpeggio or even on one note. + + Mode: InstrumentFunctionNoteStacking + octave + + Major + Majb5 + minor + minb5 + sus2 + sus4 + aug + augsus4 + tri + 6 + 6sus4 + 6add9 + m6 + m6add9 + 7 + 7sus4 + 7#5 + 7b5 + 7#9 + 7b9 + 7#5#9 + 7#5b9 + 7b5b9 + 7add11 + 7add13 + 7#11 + Maj7 + Maj7b5 + Maj7#5 + Maj7#11 + Maj7add13 + m7 + m7b5 + m7b9 + m7add11 + m7add13 + m-Maj7 + m-Maj7add11 + m-Maj7add13 + 9 + 9sus4 + add9 + 9#5 + 9b5 + 9#11 + 9b13 + Maj9 + Maj9sus4 + Maj9#5 + Maj9#11 + m9 + madd9 + m9b5 + m9-Maj7 + 11 + 11b9 + Maj11 + m11 + m-Maj11 + 13 + 13#9 + 13b9 + 13b5b9 + Maj13 + m13 + m-Maj13 + Harmonic minor + Melodic minor + Whole tone + Diminished + Major pentatonic + Minor pentatonic + Jap in sen + Major bebop + Dominant bebop + Blues + Arabic + Enigmatic + Neopolitan + Neopolitan minor + Hungarian minor + Dorian + Phrygolydian + Lydian + Mixolydian + Aeolian + Locrian - Chords + + Minor - Chord type + + Chromatic - Chord range + + Half-Whole Diminished - Minor + + 5 - Chromatic + + Phrygian dominant - Half-Whole Diminished + + Persian - 5 + + Chords - Phrygian dominant + + Chord type - Persian + + Chord range InstrumentFunctionNoteStackingView - RANGE + + STACKING - Chord range: + + Chord: - octave(s) + + RANGE - Use this knob for setting the chord range in octaves. The selected chord will be played within specified number of octaves. + + Chord range: - STACKING + + octave(s) - Chord: + + Use this knob for setting the chord range in octaves. The selected chord will be played within specified number of octaves. InstrumentMidiIOView + ENABLE MIDI INPUT + + CHANNEL + + VELOCITY + ENABLE MIDI OUTPUT + PROGRAM - MIDI devices to receive MIDI events from + + NOTE - MIDI devices to send MIDI events to + + MIDI devices to receive MIDI events from - NOTE + + MIDI devices to send MIDI events to + CUSTOM BASE VELOCITY + Specify the velocity normalization base for MIDI-based instruments at 100% note velocity + BASE VELOCITY @@ -3032,10 +3795,12 @@ You can remove and move FX channels in the context menu, which is accessed by ri InstrumentMiscView + MASTER PITCH + Enables the use of Master Pitch @@ -3043,126 +3808,158 @@ You can remove and move FX channels in the context menu, which is accessed by ri InstrumentSoundShaping + VOLUME + Volume + CUTOFF + + Cutoff frequency + RESO + Resonance + Envelopes/LFOs + Filter type + Q/Resonance + LowPass + HiPass + BandPass csg + BandPass czpg + Notch + Allpass + Moog + 2x LowPass + RC LowPass 12dB + RC BandPass 12dB + RC HighPass 12dB + RC LowPass 24dB + RC BandPass 24dB + RC HighPass 24dB + Vocal Formant Filter + 2x Moog + SV LowPass + SV BandPass + SV HighPass + SV Notch + Fast Formant + Tripole @@ -3170,50 +3967,62 @@ You can remove and move FX channels in the context menu, which is accessed by ri InstrumentSoundShapingView + TARGET + These tabs contain envelopes. They're very important for modifying a sound, in that they are almost always necessary for substractive synthesis. For example if you have a volume envelope, you can set when the sound should have a specific volume. If you want to create some soft strings then your sound has to fade in and out very softly. This can be done by setting large attack and release times. It's the same for other envelope targets like panning, cutoff frequency for the used filter and so on. Just monkey around with it! You can really make cool sounds out of a saw-wave with just some envelopes...! + FILTER + Here you can select the built-in filter you want to use for this instrument-track. Filters are very important for changing the characteristics of a sound. - Hz + + FREQ - Use this knob for setting the cutoff frequency for the selected filter. The cutoff frequency specifies the frequency for cutting the signal by a filter. For example a lowpass-filter cuts all frequencies above the cutoff frequency. A highpass-filter cuts all frequencies below cutoff frequency, and so on... + + cutoff frequency: - RESO + + Hz - Resonance: + + Use this knob for setting the cutoff frequency for the selected filter. The cutoff frequency specifies the frequency for cutting the signal by a filter. For example a lowpass-filter cuts all frequencies above the cutoff frequency. A highpass-filter cuts all frequencies below cutoff frequency, and so on... - Use this knob for setting Q/Resonance for the selected filter. Q/Resonance tells the filter how much it should amplify frequencies near Cutoff-frequency. + + RESO - FREQ + + Resonance: - cutoff frequency: + + Use this knob for setting Q/Resonance for the selected filter. Q/Resonance tells the filter how much it should amplify frequencies near Cutoff-frequency. + Envelopes, LFOs and filters are not supported by the current instrument. @@ -3221,42 +4030,54 @@ You can remove and move FX channels in the context menu, which is accessed by ri InstrumentTrack - unnamed_track + + + Default preset - Volume + + With this knob you can set the volume of the opened channel. - Panning + + + unnamed_track - Pitch + + Base note - FX channel + + Volume - Default preset + + Panning - With this knob you can set the volume of the opened channel. + + Pitch - Base note + + Pitch range - Pitch range + + FX channel + Master Pitch @@ -3264,42 +4085,52 @@ You can remove and move FX channels in the context menu, which is accessed by ri InstrumentTrackView + Volume + Volume: + VOL + Panning + Panning: + PAN + MIDI + Input + Output + FX %1: %2 @@ -3307,125 +4138,156 @@ You can remove and move FX channels in the context menu, which is accessed by ri InstrumentTrackWindow + GENERAL SETTINGS + + Use these controls to view and edit the next/previous track in the song editor. + + + + Instrument volume + Volume: + VOL + Panning + Panning: + PAN + Pitch + Pitch: + cents + PITCH - FX channel + + Pitch range (semitones) - ENV/LFO + + RANGE - FUNC + + FX channel + + FX - MIDI - - - - Save preset + + Save current instrument track settings in a preset file - XML preset file (*.xpf) + + Click here, if you want to save current instrument track settings in a preset file. Later you can load this preset by double-clicking it in the preset-browser. - PLUGIN + + SAVE - Pitch range (semitones) + + ENV/LFO - RANGE + + FUNC - Save current instrument track settings in a preset file + + MIDI - Click here, if you want to save current instrument track settings in a preset file. Later you can load this preset by double-clicking it in the preset-browser. + + MISC - MISC + + Save preset - Use these controls to view and edit the next/previous track in the song editor. + + XML preset file (*.xpf) - SAVE + + PLUGIN Knob + Set linear + Set logarithmic + Please enter a new value between -96.0 dBFS and 6.0 dBFS: + Please enter a new value between %1 and %2: @@ -3433,6 +4295,7 @@ You can remove and move FX channels in the context menu, which is accessed by ri LadspaControl + Link channels @@ -3440,10 +4303,12 @@ You can remove and move FX channels in the context menu, which is accessed by ri LadspaControlDialog + Link Channels + Channel @@ -3451,14 +4316,17 @@ You can remove and move FX channels in the context menu, which is accessed by ri LadspaControlView + Link channels + Value: + Sorry, no help available. @@ -3466,6 +4334,7 @@ You can remove and move FX channels in the context menu, which is accessed by ri LadspaEffect + Unknown LADSPA plugin %1 requested. @@ -3473,6 +4342,7 @@ You can remove and move FX channels in the context menu, which is accessed by ri LcdSpinBox + Please enter a new value between %1 and %2: @@ -3480,18 +4350,26 @@ You can remove and move FX channels in the context menu, which is accessed by ri LeftRightNav + + + Previous + + + Next + Previous (%1) + Next (%1) @@ -3499,30 +4377,37 @@ You can remove and move FX channels in the context menu, which is accessed by ri LfoController + LFO Controller + Base value + Oscillator speed + Oscillator amount + Oscillator phase + Oscillator waveform + Frequency Multiplier @@ -3530,114 +4415,141 @@ You can remove and move FX channels in the context menu, which is accessed by ri LfoControllerDialog + LFO + LFO Controller + BASE + Base amount: + todo + SPD + LFO-speed: + Use this knob for setting speed of the LFO. The bigger this value the faster the LFO oscillates and the faster the effect. + + AMNT + + + + Modulation amount: + Use this knob for setting modulation amount of the LFO. The bigger this value, the more the connected control (e.g. volume or cutoff-frequency) will be influenced by the LFO. + PHS + Phase offset: + degrees + With this knob you can set the phase offset of the LFO. That means you can move the point within an oscillation where the oscillator begins to oscillate. For example if you have a sine-wave and have a phase-offset of 180 degrees the wave will first go down. It's the same with a square-wave. + Click here for a sine-wave. + Click here for a triangle-wave. + Click here for a saw-wave. + Click here for a square-wave. + + Click here for a moog saw-wave. + + + + Click here for an exponential wave. + Click here for white-noise. + Click here for a user-defined shape. Double click to pick a file. - - Click here for a moog saw-wave. - - - - AMNT - - LmmsCore + Generating wavetables + Initializing data structures + Opening audio and midi devices + Launching mixer threads @@ -3645,384 +4557,495 @@ Double click to pick a file. MainWindow - Could not save config-file + + Configuration file - Could not save configuration file %1. You're probably not permitted to write to this file. -Please make sure you have write-access to the file and try again. + + Error while parsing configuration file at line %1:%2: %3 - &New + + Could not save config-file - &Open... + + Could not save configuration file %1. You're probably not permitted to write to this file. +Please make sure you have write-access to the file and try again. - &Save + + Project recovery - Save &As... + + There is a recovery file present. It looks like the last session did not end properly or another instance of LMMS is already running. Do you want to recover the project of this session? - Import... + + + + Recover - E&xport... + + Recover the file. Please don't run multiple instances of LMMS when you do this. - &Quit + + + + Ignore - &Edit + + Launch LMMS as usual but with automatic backup disabled to prevent the present recover file from being overwritten. - Settings + + + + Discard - &Tools + + Launch a default session and delete the restored files. This is not reversible. - &Help + + Version %1 - Help + + Preparing plugin browser - What's this? + + Preparing file browsers - About + + My Projects - Create new project + + My Samples - Create new project from template + + My Presets - Open existing project + + My Home - Recently opened projects + + Root directory - Save current project + + Volumes - Export current project + + My Computer - Song Editor + + Loading background artwork - By pressing this button, you can show or hide the Song-Editor. With the help of the Song-Editor you can edit song-playlist and specify when which track should be played. You can also insert and move samples (e.g. rap samples) directly into the playlist. + + &File - Beat+Bassline Editor + + &New - By pressing this button, you can show or hide the Beat+Bassline Editor. The Beat+Bassline Editor is needed for creating beats, and for opening, adding, and removing channels, and for cutting, copying and pasting beat and bassline-patterns, and for other things like that. + + New from template - Piano Roll + + &Open... - Click here to show or hide the Piano-Roll. With the help of the Piano-Roll you can edit melodies in an easy way. + + &Recently Opened Projects - Automation Editor + + &Save - Click here to show or hide the Automation Editor. With the help of the Automation Editor you can edit dynamic values in an easy way. + + Save &As... - FX Mixer + + Save as New &Version - Click here to show or hide the FX Mixer. The FX Mixer is a very powerful tool for managing effects for your song. You can insert effects into different effect-channels. + + Save as default template - Project Notes + + Import... - Click here to show or hide the project notes window. In this window you can put down your project notes. + + E&xport... - Controller Rack + + E&xport Tracks... - Untitled + + Export &MIDI... - LMMS %1 + + &Quit - Project not saved + + &Edit - The current project was modified since last saving. Do you want to save it now? + + Undo - Help not available + + Redo - Currently there's no help available in LMMS. -Please visit http://lmms.sf.net/wiki for documentation on LMMS. + + Settings - LMMS (*.mmp *.mmpz) + + &View - Version %1 + + &Tools - Configuration file + + &Help - Error while parsing configuration file at line %1:%2: %3 + + Online Help - Volumes + + Help - Undo + + What's This? - Redo + + About - My Projects + + Create new project - My Samples + + Create new project from template - My Presets + + Open existing project - My Home + + Recently opened projects - My Computer + + Save current project - &File + + Export current project - &Recently Opened Projects + + What's this? - Save as New &Version + + Toggle metronome - E&xport Tracks... + + Show/hide Song-Editor - Online Help + + By pressing this button, you can show or hide the Song-Editor. With the help of the Song-Editor you can edit song-playlist and specify when which track should be played. You can also insert and move samples (e.g. rap samples) directly into the playlist. - What's This? + + Show/hide Beat+Bassline Editor - Open Project + + By pressing this button, you can show or hide the Beat+Bassline Editor. The Beat+Bassline Editor is needed for creating beats, and for opening, adding, and removing channels, and for cutting, copying and pasting beat and bassline-patterns, and for other things like that. - Save Project + + Show/hide Piano-Roll - Project recovery + + Click here to show or hide the Piano-Roll. With the help of the Piano-Roll you can edit melodies in an easy way. - There is a recovery file present. It looks like the last session did not end properly or another instance of LMMS is already running. Do you want to recover the project of this session? + + Show/hide Automation Editor - Recover + + Click here to show or hide the Automation Editor. With the help of the Automation Editor you can edit dynamic values in an easy way. - Recover the file. Please don't run multiple instances of LMMS when you do this. + + Show/hide FX Mixer - Ignore + + Click here to show or hide the FX Mixer. The FX Mixer is a very powerful tool for managing effects for your song. You can insert effects into different effect-channels. - Launch LMMS as usual but with automatic backup disabled to prevent the present recover file from being overwritten. + + Show/hide project notes - Discard + + Click here to show or hide the project notes window. In this window you can put down your project notes. - Launch a default session and delete the restored files. This is not reversible. + + Show/hide controller rack - Preparing plugin browser + + Untitled - Preparing file browsers + + Recover session. Please save your work! - Root directory + + Automatic backup disabled. Remember to save your work! - Loading background artwork + + LMMS %1 - New from template + + Recovered project not saved - Save as default template + + This project was recovered from the previous session. It is currently unsaved and will be lost if you don't save it. Do you want to save it now? - &View + + Project not saved - Toggle metronome + + The current project was modified since last saving. Do you want to save it now? - Show/hide Song-Editor + + Open Project - Show/hide Beat+Bassline Editor + + LMMS (*.mmp *.mmpz) - Show/hide Piano-Roll + + Save Project - Show/hide Automation Editor + + LMMS Project - Show/hide FX Mixer + + LMMS Project Template - Show/hide project notes + + Save project template - Show/hide controller rack + + Overwrite default template? - Recover session. Please save your work! + + This will overwrite your current default template. - Automatic backup disabled. Remember to save your work! + + Help not available - Recovered project not saved + + Currently there's no help available in LMMS. +Please visit http://lmms.sf.net/wiki for documentation on LMMS. - This project was recovered from the previous session. It is currently unsaved and will be lost if you don't save it. Do you want to save it now? + + Song Editor - LMMS Project + + Beat+Bassline Editor - LMMS Project Template + + Piano Roll - Overwrite default template? + + Automation Editor - This will overwrite your current default template. + + FX Mixer + + Project Notes + + + + + Controller Rack + + + + Volume as dBFS + Smooth scroll + Enable note labels in piano roll @@ -4030,14 +5053,19 @@ Please visit http://lmms.sf.net/wiki for documentation on LMMS. MeterDialog + + Meter Numerator + + Meter Denominator + TIME SIG @@ -4045,10 +5073,12 @@ Please visit http://lmms.sf.net/wiki for documentation on LMMS. MeterModel + Numerator + Denominator @@ -4056,41 +5086,26 @@ Please visit http://lmms.sf.net/wiki for documentation on LMMS. MidiController + MIDI Controller + unnamed_midi_controller - - MidiImport - - Setup incomplete - - - - You do not have set up a default soundfont in the settings dialog (Edit->Settings). Therefore no sound will be played back after importing this MIDI file. You should download a General MIDI soundfont, specify it in settings dialog and try again. - - - - You did not compile LMMS with support for SoundFont2 player, which is used to add default sound to imported MIDI files. Therefore no sound will be played back after importing this MIDI file. - - - - Track - - - MidiJack + JACK server down When JACK(JACK Audio Connection Kit) disconnects, it will show the following message (title) + The JACK server seems to be shuted down. When JACK(JACK Audio Connection Kit) disconnects, it will show the following message (dialog message) @@ -4099,53 +5114,65 @@ Please visit http://lmms.sf.net/wiki for documentation on LMMS. MidiPort + Input channel + Output channel + Input controller + Output controller + Fixed input velocity + Fixed output velocity - Output MIDI program + + Fixed output note - Receive MIDI-events + + Output MIDI program - Send MIDI-events + + Base velocity - Fixed output note + + Receive MIDI-events - Base velocity + + Send MIDI-events MidiSetupWidget + DEVICE @@ -4153,474 +5180,595 @@ Please visit http://lmms.sf.net/wiki for documentation on LMMS. MonstroInstrument + Osc 1 Volume + Osc 1 Panning + Osc 1 Coarse detune + Osc 1 Fine detune left + Osc 1 Fine detune right + Osc 1 Stereo phase offset + Osc 1 Pulse width + Osc 1 Sync send on rise + Osc 1 Sync send on fall + Osc 2 Volume + Osc 2 Panning + Osc 2 Coarse detune + Osc 2 Fine detune left + Osc 2 Fine detune right + Osc 2 Stereo phase offset + Osc 2 Waveform + Osc 2 Sync Hard + Osc 2 Sync Reverse + Osc 3 Volume + Osc 3 Panning + Osc 3 Coarse detune + Osc 3 Stereo phase offset + Osc 3 Sub-oscillator mix + Osc 3 Waveform 1 + Osc 3 Waveform 2 + Osc 3 Sync Hard + Osc 3 Sync Reverse + LFO 1 Waveform + LFO 1 Attack + LFO 1 Rate + LFO 1 Phase + LFO 2 Waveform + LFO 2 Attack + LFO 2 Rate + LFO 2 Phase + Env 1 Pre-delay + Env 1 Attack + Env 1 Hold + Env 1 Decay + Env 1 Sustain + Env 1 Release + Env 1 Slope + Env 2 Pre-delay + Env 2 Attack + Env 2 Hold + Env 2 Decay + Env 2 Sustain + Env 2 Release + Env 2 Slope + Osc2-3 modulation + Selected view + Vol1-Env1 + Vol1-Env2 + Vol1-LFO1 + Vol1-LFO2 + Vol2-Env1 + Vol2-Env2 + Vol2-LFO1 + Vol2-LFO2 + Vol3-Env1 + Vol3-Env2 + Vol3-LFO1 + Vol3-LFO2 + Phs1-Env1 + Phs1-Env2 + Phs1-LFO1 + Phs1-LFO2 + Phs2-Env1 + Phs2-Env2 + Phs2-LFO1 + Phs2-LFO2 + Phs3-Env1 + Phs3-Env2 + Phs3-LFO1 + Phs3-LFO2 + Pit1-Env1 + Pit1-Env2 + Pit1-LFO1 + Pit1-LFO2 + Pit2-Env1 + Pit2-Env2 + Pit2-LFO1 + Pit2-LFO2 + Pit3-Env1 + Pit3-Env2 + Pit3-LFO1 + Pit3-LFO2 + PW1-Env1 + PW1-Env2 + PW1-LFO1 + PW1-LFO2 + Sub3-Env1 + Sub3-Env2 + Sub3-LFO1 + Sub3-LFO2 + + Sine wave + Bandlimited Triangle wave + Bandlimited Saw wave + Bandlimited Ramp wave + Bandlimited Square wave + Bandlimited Moog saw wave + + Soft square wave + Absolute sine wave + + Exponential wave + White noise + Digital Triangle wave + Digital Saw wave + Digital Ramp wave + Digital Square wave + Digital Moog saw wave + Triangle wave + Saw wave + Ramp wave + Square wave + Moog saw wave + Abs. sine wave + Random + Random smooth @@ -4628,20 +5776,24 @@ Please visit http://lmms.sf.net/wiki for documentation on LMMS. MonstroView + Operators view + The Operators view contains all the operators. These include both audible operators (oscillators) and inaudible operators, or modulators: Low-frequency oscillators and Envelopes. Knobs and other widgets in the Operators view have their own what's this -texts, so you can get more specific help for them that way. + Matrix view + The Matrix view contains the modulation matrix. Here you can define the modulation relationships between the various operators: Each audible operator (oscillators 1-3) has 3-4 properties that can be modulated by any of the modulators. Using more modulations consumes more CPU power. The view is divided to modulation targets, grouped by the target oscillator. Available targets are volume, pitch, phase, pulse width and sub-osc ratio. Note: some targets are specific to one oscillator only. @@ -4650,256 +5802,407 @@ Each modulation target has 4 knobs, one for each modulator. By default the knobs - Mix Osc2 with Osc3 + + + + Volume - Modulate amplitude of Osc3 with Osc2 + + + + Panning - Modulate frequency of Osc3 with Osc2 + + + + Coarse detune - Modulate phase of Osc3 with Osc2 + + + + semitones - The CRS knob changes the tuning of oscillator 1 in semitone steps. + + + Finetune left - The CRS knob changes the tuning of oscillator 2 in semitone steps. + + + + + cents - The CRS knob changes the tuning of oscillator 3 in semitone steps. + + + Finetune right - FTL and FTR change the finetuning of the oscillator for left and right channels respectively. These can add stereo-detuning to the oscillator which widens the stereo image and causes an illusion of space. + + + + Stereo phase offset - The SPO knob modifies the difference in phase between left and right channels. Higher difference creates a wider stereo image. + + + + + + deg - The PW knob controls the pulse width, also known as duty cycle, of oscillator 1. Oscillator 1 is a digital pulse wave oscillator, it doesn't produce bandlimited output, which means that you can use it as an audible oscillator but it will cause aliasing. You can also use it as an inaudible source of a sync signal, which can be used to synchronize oscillators 2 and 3. + + Pulse width - Send Sync on Rise: When enabled, the Sync signal is sent every time the state of oscillator 1 changes from low to high, ie. when the amplitude changes from -1 to 1. Oscillator 1's pitch, phase and pulse width may affect the timing of syncs, but its volume has no effect on them. Sync signals are sent independently for both left and right channels. + + Send sync on pulse rise - Send Sync on Fall: When enabled, the Sync signal is sent every time the state of oscillator 1 changes from high to low, ie. when the amplitude changes from 1 to -1. Oscillator 1's pitch, phase and pulse width may affect the timing of syncs, but its volume has no effect on them. Sync signals are sent independently for both left and right channels. + + Send sync on pulse fall - Hard sync: Every time the oscillator receives a sync signal from oscillator 1, its phase is reset to 0 + whatever its phase offset is. + + Hard sync oscillator 2 - Reverse sync: Every time the oscillator receives a sync signal from oscillator 1, the amplitude of the oscillator gets inverted. + + Reverse sync oscillator 2 - Choose waveform for oscillator 2. + + Sub-osc mix - Choose waveform for oscillator 3's first sub-osc. Oscillator 3 can smoothly interpolate between two different waveforms. + + Hard sync oscillator 3 - Choose waveform for oscillator 3's second sub-osc. Oscillator 3 can smoothly interpolate between two different waveforms. + + Reverse sync oscillator 3 - The SUB knob changes the mixing ratio of the two sub-oscs of oscillator 3. Each sub-osc can be set to produce a different waveform, and oscillator 3 can smoothly interpolate between them. All incoming modulations to oscillator 3 are applied to both sub-oscs/waveforms in the exact same way. + + + + + Attack - In addition to dedicated modulators, Monstro allows oscillator 3 to be modulated by the output of oscillator 2. - -Mix mode means no modulation: the outputs of the oscillators are simply mixed together. + + + Rate - In addition to dedicated modulators, Monstro allows oscillator 3 to be modulated by the output of oscillator 2. - -AM means amplitude modulation: Oscillator 3's amplitude (volume) is modulated by oscillator 2. + + + Phase - In addition to dedicated modulators, Monstro allows oscillator 3 to be modulated by the output of oscillator 2. - -FM means frequency modulation: Oscillator 3's frequency (pitch) is modulated by oscillator 2. The frequency modulation is implemented as phase modulation, which gives a more stable overall pitch than "pure" frequency modulation. + + + Pre-delay - In addition to dedicated modulators, Monstro allows oscillator 3 to be modulated by the output of oscillator 2. - -PM means phase modulation: Oscillator 3's phase is modulated by oscillator 2. It differs from frequency modulation in that the phase changes are not cumulative. + + + Hold - Select the waveform for LFO 1. -"Random" and "Random smooth" are special waveforms: they produce random output, where the rate of the LFO controls how often the state of the LFO changes. The smooth version interpolates between these states with cosine interpolation. These random modes can be used to give "life" to your presets - add some of that analog unpredictability... + + + Decay - Select the waveform for LFO 2. -"Random" and "Random smooth" are special waveforms: they produce random output, where the rate of the LFO controls how often the state of the LFO changes. The smooth version interpolates between these states with cosine interpolation. These random modes can be used to give "life" to your presets - add some of that analog unpredictability... + + + Sustain - Attack causes the LFO to come on gradually from the start of the note. + + + Release - Rate sets the speed of the LFO, measured in milliseconds per cycle. Can be synced to tempo. + + + Slope - PHS controls the phase offset of the LFO. + + Mix Osc2 with Osc3 - PRE, or pre-delay, delays the start of the envelope from the start of the note. 0 means no delay. + + Modulate amplitude of Osc3 with Osc2 - ATT, or attack, controls how fast the envelope ramps up at start, measured in milliseconds. A value of 0 means instant. + + Modulate frequency of Osc3 with Osc2 - HOLD controls how long the envelope stays at peak after the attack phase. + + Modulate phase of Osc3 with Osc2 - DEC, or decay, controls how fast the envelope falls off from its peak, measured in milliseconds it would take to go from peak to zero. The actual decay may be shorter if sustain is used. + + The CRS knob changes the tuning of oscillator 1 in semitone steps. - SUS, or sustain, controls the sustain level of the envelope. The decay phase will not go below this level as long as the note is held. + + The CRS knob changes the tuning of oscillator 2 in semitone steps. - REL, or release, controls how long the release is for the note, measured in how long it would take to fall from peak to zero. Actual release may be shorter, depending on at what phase the note is released. + + The CRS knob changes the tuning of oscillator 3 in semitone steps. - The slope knob controls the curve or shape of the envelope. A value of 0 creates straight rises and falls. Negative values create curves that start slowly, peak quickly and fall of slowly again. Positive values create curves that start and end quickly, and stay longer near the peaks. + + + + + FTL and FTR change the finetuning of the oscillator for left and right channels respectively. These can add stereo-detuning to the oscillator which widens the stereo image and causes an illusion of space. - Volume + + + + The SPO knob modifies the difference in phase between left and right channels. Higher difference creates a wider stereo image. - Panning + + The PW knob controls the pulse width, also known as duty cycle, of oscillator 1. Oscillator 1 is a digital pulse wave oscillator, it doesn't produce bandlimited output, which means that you can use it as an audible oscillator but it will cause aliasing. You can also use it as an inaudible source of a sync signal, which can be used to synchronize oscillators 2 and 3. - Coarse detune + + Send Sync on Rise: When enabled, the Sync signal is sent every time the state of oscillator 1 changes from low to high, ie. when the amplitude changes from -1 to 1. Oscillator 1's pitch, phase and pulse width may affect the timing of syncs, but its volume has no effect on them. Sync signals are sent independently for both left and right channels. - semitones + + Send Sync on Fall: When enabled, the Sync signal is sent every time the state of oscillator 1 changes from high to low, ie. when the amplitude changes from 1 to -1. Oscillator 1's pitch, phase and pulse width may affect the timing of syncs, but its volume has no effect on them. Sync signals are sent independently for both left and right channels. - Finetune left + + + Hard sync: Every time the oscillator receives a sync signal from oscillator 1, its phase is reset to 0 + whatever its phase offset is. - cents + + + Reverse sync: Every time the oscillator receives a sync signal from oscillator 1, the amplitude of the oscillator gets inverted. - Finetune right + + Choose waveform for oscillator 2. - Stereo phase offset + + Choose waveform for oscillator 3's first sub-osc. Oscillator 3 can smoothly interpolate between two different waveforms. - deg + + Choose waveform for oscillator 3's second sub-osc. Oscillator 3 can smoothly interpolate between two different waveforms. - Pulse width + + The SUB knob changes the mixing ratio of the two sub-oscs of oscillator 3. Each sub-osc can be set to produce a different waveform, and oscillator 3 can smoothly interpolate between them. All incoming modulations to oscillator 3 are applied to both sub-oscs/waveforms in the exact same way. - Send sync on pulse rise + + In addition to dedicated modulators, Monstro allows oscillator 3 to be modulated by the output of oscillator 2. + +Mix mode means no modulation: the outputs of the oscillators are simply mixed together. - Send sync on pulse fall + + In addition to dedicated modulators, Monstro allows oscillator 3 to be modulated by the output of oscillator 2. + +AM means amplitude modulation: Oscillator 3's amplitude (volume) is modulated by oscillator 2. - Hard sync oscillator 2 + + In addition to dedicated modulators, Monstro allows oscillator 3 to be modulated by the output of oscillator 2. + +FM means frequency modulation: Oscillator 3's frequency (pitch) is modulated by oscillator 2. The frequency modulation is implemented as phase modulation, which gives a more stable overall pitch than "pure" frequency modulation. - Reverse sync oscillator 2 + + In addition to dedicated modulators, Monstro allows oscillator 3 to be modulated by the output of oscillator 2. + +PM means phase modulation: Oscillator 3's phase is modulated by oscillator 2. It differs from frequency modulation in that the phase changes are not cumulative. - Sub-osc mix + + Select the waveform for LFO 1. +"Random" and "Random smooth" are special waveforms: they produce random output, where the rate of the LFO controls how often the state of the LFO changes. The smooth version interpolates between these states with cosine interpolation. These random modes can be used to give "life" to your presets - add some of that analog unpredictability... - Hard sync oscillator 3 + + Select the waveform for LFO 2. +"Random" and "Random smooth" are special waveforms: they produce random output, where the rate of the LFO controls how often the state of the LFO changes. The smooth version interpolates between these states with cosine interpolation. These random modes can be used to give "life" to your presets - add some of that analog unpredictability... - Reverse sync oscillator 3 + + + Attack causes the LFO to come on gradually from the start of the note. - Attack + + + Rate sets the speed of the LFO, measured in milliseconds per cycle. Can be synced to tempo. - Rate + + + PHS controls the phase offset of the LFO. - Phase + + + PRE, or pre-delay, delays the start of the envelope from the start of the note. 0 means no delay. - Pre-delay + + + ATT, or attack, controls how fast the envelope ramps up at start, measured in milliseconds. A value of 0 means instant. - Hold + + + HOLD controls how long the envelope stays at peak after the attack phase. - Decay + + + DEC, or decay, controls how fast the envelope falls off from its peak, measured in milliseconds it would take to go from peak to zero. The actual decay may be shorter if sustain is used. - Sustain + + + SUS, or sustain, controls the sustain level of the envelope. The decay phase will not go below this level as long as the note is held. - Release + + + REL, or release, controls how long the release is for the note, measured in how long it would take to fall from peak to zero. Actual release may be shorter, depending on at what phase the note is released. - Slope + + + The slope knob controls the curve or shape of the envelope. A value of 0 creates straight rises and falls. Negative values create curves that start slowly, peak quickly and fall of slowly again. Positive values create curves that start and end quickly, and stay longer near the peaks. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Modulation amount @@ -4907,34 +6210,42 @@ PM means phase modulation: Oscillator 3's phase is modulated by oscillator MultitapEchoControlDialog + Length + Step length: + Dry + Dry Gain: + Stages + Lowpass stages: + Swap inputs + Swap left and right input channel for reflections @@ -4942,82 +6253,102 @@ PM means phase modulation: Oscillator 3's phase is modulated by oscillator NesInstrument + Channel 1 Coarse detune + Channel 1 Volume + Channel 1 Envelope length + Channel 1 Duty cycle + Channel 1 Sweep amount + Channel 1 Sweep rate + Channel 2 Coarse detune + Channel 2 Volume + Channel 2 Envelope length + Channel 2 Duty cycle + Channel 2 Sweep amount + Channel 2 Sweep rate + Channel 3 Coarse detune + Channel 3 Volume + Channel 4 Volume + Channel 4 Envelope length + Channel 4 Noise frequency + Channel 4 Noise frequency sweep + Master volume + Vibrato @@ -5025,114 +6356,155 @@ PM means phase modulation: Oscillator 3's phase is modulated by oscillator NesInstrumentView + + + + Volume + + + Coarse detune + + + Envelope length + Enable channel 1 + Enable envelope 1 + Enable envelope 1 loop + Enable sweep 1 + + Sweep amount + + Sweep rate + + 12.5% Duty cycle + + 25% Duty cycle + + 50% Duty cycle + + 75% Duty cycle + Enable channel 2 + Enable envelope 2 + Enable envelope 2 loop + Enable sweep 2 + Enable channel 3 + Noise Frequency + Frequency sweep + Enable channel 4 + Enable envelope 4 + Enable envelope 4 loop + Quantize noise frequency when using note frequency + Use note frequency for noise + Noise mode + Master Volume + Vibrato @@ -5140,81 +6512,103 @@ PM means phase modulation: Oscillator 3's phase is modulated by oscillator OscillatorObject + + Osc %1 waveform + + + + + Osc %1 harmonic + + + + + Osc %1 volume + + Osc %1 panning - Osc %1 coarse detuning + + + Osc %1 fine detuning left - Osc %1 fine detuning left + + Osc %1 coarse detuning + Osc %1 fine detuning right + Osc %1 phase-offset + Osc %1 stereo phase-detuning + Osc %1 wave shape + Modulation type %1 - - Osc %1 waveform - - - - Osc %1 harmonic - - PatchesDialog + Qsynth: Channel Preset + Bank selector + Bank + Program selector + Patch + Name + OK + Cancel @@ -5222,46 +6616,57 @@ PM means phase modulation: Oscillator 3's phase is modulated by oscillator PatmanView + Open other patch + Click here to open another patch-file. Loop and Tune settings are not reset. + Loop + Loop mode + Here you can toggle the Loop mode. If enabled, PatMan will use the loop information available in the file. + Tune + Tune mode + Here you can toggle the Tune mode. If enabled, PatMan will tune the sample to match the note's frequency. + No file selected + Open patch file + Patch-Files (*.pat) @@ -5269,38 +6674,47 @@ PM means phase modulation: Oscillator 3's phase is modulated by oscillator PatternView - Open in piano-roll + + use mouse wheel to set velocity of a step - Clear all notes + + double-click to open in Piano Roll - Reset name + + Open in piano-roll - Change name + + Clear all notes - Add steps + + Reset name - Remove steps + + Change name - use mouse wheel to set velocity of a step + + Add steps - double-click to open in Piano Roll + + Remove steps + Clone Steps @@ -5308,14 +6722,17 @@ PM means phase modulation: Oscillator 3's phase is modulated by oscillator PeakController + Peak Controller + Peak Controller Bug + Due to a bug in older version of LMMS, the peak controllers may not be connect properly. Please ensure that peak controllers are connected properly and re-save this file. Sorry for any inconvenience caused. @@ -5323,10 +6740,12 @@ PM means phase modulation: Oscillator 3's phase is modulated by oscillator PeakControllerDialog + PEAK + LFO Controller @@ -5334,306 +6753,382 @@ PM means phase modulation: Oscillator 3's phase is modulated by oscillator PeakControllerEffectControlDialog + BASE + Base amount: - Modulation amount: + + AMNT - Attack: + + Modulation amount: - Release: + + MULT - AMNT + + Amount Multiplicator: - MULT + + ATCK - Amount Multiplicator: + + Attack: - ATCK + + DCAY - DCAY + + Release: - Treshold: + + TRSH - TRSH + + Treshold: PeakControllerEffectControls + Base value + Modulation amount - Mute output + + Attack - Attack + + Release - Release + + Treshold - Abs Value + + Mute output - Amount Multiplicator + + Abs Value - Treshold + + Amount Multiplicator PianoRoll - Please open a pattern by double-clicking on it! + + Note Velocity - Last note + + Note Panning - Note lock + + Mark/unmark current semitone - Note Velocity + + Mark/unmark all corresponding octave semitones - Note Panning + + Mark current scale - Mark/unmark current semitone + + Mark current chord - Mark current scale + + Unmark all - Mark current chord + + Select all notes on this key - Unmark all + + Note lock + + + + + Last note + No scale + No chord + Velocity: %1% + Panning: %1% left + Panning: %1% right + Panning: center - Please enter a new value between %1 and %2: - - - - Mark/unmark all corresponding octave semitones + + Please open a pattern by double-clicking on it! - Select all notes on this key + + + Please enter a new value between %1 and %2: PianoRollWindow + Play/pause current pattern (Space) + Record notes from MIDI-device/channel-piano + Record notes from MIDI-device/channel-piano while playing song or BB track + Stop playing of current pattern (Space) + Click here to play the current pattern. This is useful while editing it. The pattern is automatically looped when its end is reached. + Click here to record notes from a MIDI-device or the virtual test-piano of the according channel-window to the current pattern. When recording all notes you play will be written to this pattern and you can play and edit them afterwards. + Click here to record notes from a MIDI-device or the virtual test-piano of the according channel-window to the current pattern. When recording all notes you play will be written to this pattern and you will hear the song or BB track in the background. + Click here to stop playback of current pattern. + + Edit actions + + + + Draw mode (Shift+D) + Erase mode (Shift+E) + Select mode (Shift+S) + Detune mode (Shift+T) + Click here and draw mode will be activated. In this mode you can add, resize and move notes. This is the default mode which is used most of the time. You can also press 'Shift+D' on your keyboard to activate this mode. In this mode, hold %1 to temporarily go into select mode. + Click here and erase mode will be activated. In this mode you can erase notes. You can also press 'Shift+E' on your keyboard to activate this mode. + Click here and select mode will be activated. In this mode you can select notes. Alternatively, you can hold %1 in draw mode to temporarily use select mode. + Click here and detune mode will be activated. In this mode you can click a note to open its automation detuning. You can utilize this to slide notes from one to another. You can also press 'Shift+T' on your keyboard to activate this mode. - Cut selected notes (%1+X) + + Quantize - Copy selected notes (%1+C) + + Copy paste controls - Paste notes from clipboard (%1+V) + + Cut selected notes (%1+X) - Click here and the selected notes will be cut into the clipboard. You can paste them anywhere in any pattern by clicking on the paste button. + + Copy selected notes (%1+C) - Click here and the selected notes will be copied into the clipboard. You can paste them anywhere in any pattern by clicking on the paste button. + + Paste notes from clipboard (%1+V) - Click here and the notes from the clipboard will be pasted at the first visible measure. + + Click here and the selected notes will be cut into the clipboard. You can paste them anywhere in any pattern by clicking on the paste button. - This controls the magnification of an axis. It can be helpful to choose magnification for a specific task. For ordinary editing, the magnification should be fitted to your smallest notes. + + Click here and the selected notes will be copied into the clipboard. You can paste them anywhere in any pattern by clicking on the paste button. - The 'Q' stands for quantization, and controls the grid size notes and control points snap to. With smaller quantization values, you can draw shorter notes in Piano Roll, and more exact control points in the Automation Editor. + + Click here and the notes from the clipboard will be pasted at the first visible measure. - This lets you select the length of new notes. 'Last Note' means that LMMS will use the note length of the note you last edited + + Timeline controls - The feature is directly connected to the context-menu on the virtual keyboard, to the left in Piano Roll. After you have chosen the scale you want in this drop-down menu, you can right click on a desired key in the virtual keyboard, and then choose 'Mark current Scale'. LMMS will highlight all notes that belongs to the chosen scale, and in the key you have selected! + + Zoom and note controls - Let you select a chord which LMMS then can draw or highlight.You can find the most common chords in this drop-down menu. After you have selected a chord, click anywhere to place the chord, and right click on the virtual keyboard to open context menu and highlight the chord. To return to single note placement, you need to choose 'No chord' in this drop-down menu. + + This controls the magnification of an axis. It can be helpful to choose magnification for a specific task. For ordinary editing, the magnification should be fitted to your smallest notes. - Edit actions + + The 'Q' stands for quantization, and controls the grid size notes and control points snap to. With smaller quantization values, you can draw shorter notes in Piano Roll, and more exact control points in the Automation Editor. - Copy paste controls + + This lets you select the length of new notes. 'Last Note' means that LMMS will use the note length of the note you last edited - Timeline controls + + The feature is directly connected to the context-menu on the virtual keyboard, to the left in Piano Roll. After you have chosen the scale you want in this drop-down menu, you can right click on a desired key in the virtual keyboard, and then choose 'Mark current Scale'. LMMS will highlight all notes that belongs to the chosen scale, and in the key you have selected! - Zoom and note controls + + Let you select a chord which LMMS then can draw or highlight.You can find the most common chords in this drop-down menu. After you have selected a chord, click anywhere to place the chord, and right click on the virtual keyboard to open context menu and highlight the chord. To return to single note placement, you need to choose 'No chord' in this drop-down menu. + + Piano-Roll - %1 + + Piano-Roll - no pattern - - Quantize - - PianoView + Base note @@ -5641,19 +7136,23 @@ PM means phase modulation: Oscillator 3's phase is modulated by oscillator Plugin + Plugin not found + The plugin "%1" wasn't found or could not be loaded! Reason: "%2" + Error while loading plugin + Failed to load plugin "%1"! @@ -5661,14 +7160,17 @@ Reason: "%2" PluginBrowser + Instrument plugins + Instrument browser + Drag an instrument into either the Song-Editor, the Beat+Bassline Editor or into an existing instrument track. @@ -5676,10 +7178,12 @@ Reason: "%2" PluginFactory + Plugin not found. + LMMS plugin %1 does not have a plugin descriptor named %2! @@ -5687,118 +7191,147 @@ Reason: "%2" ProjectNotes + Project notes + Put down your project notes here. + Edit Actions + &Undo + %1+Z + &Redo + %1+Y + &Copy + %1+C + Cu&t + %1+X + &Paste + %1+V + Format Actions + &Bold + %1+B + &Italic + %1+I + &Underline + %1+U + &Left + %1+L + C&enter + %1+E + &Right + %1+R + &Justify + %1+J + &Color... @@ -5806,68 +7339,110 @@ Reason: "%2" ProjectRenderer + WAV-File (*.wav) + Compressed OGG-File (*.ogg) + + QGuiApplication + + + Cancel + + + QWidget + + + Name: + + Maker: + + Copyright: + + Requires Real Time: + + + + + + Yes + + + + + + No + + Real Time Capable: + + In Place Broken: + + Channels In: + + Channels Out: - File: + + File: %1 - File: %1 + + File: RenameDialog + Rename... @@ -5875,73 +7450,90 @@ Reason: "%2" SampleBuffer + Open audio file + + All Audio-Files (*.wav *.ogg *.ds *.flac *.spx *.voc *.aif *.aiff *.au *.raw) + + + + Wave-Files (*.wav) + OGG-Files (*.ogg) + DrumSynth-Files (*.ds) + FLAC-Files (*.flac) + SPEEX-Files (*.spx) + VOC-Files (*.voc) + AIFF-Files (*.aif *.aiff) + AU-Files (*.au) + RAW-Files (*.raw) - - All Audio-Files (*.wav *.ogg *.ds *.flac *.spx *.voc *.aif *.aiff *.au *.raw) - - SampleTCOView + double-click to select sample + Delete (middle mousebutton) + Cut + Copy + Paste + Mute/unmute (<%1> + middle click) @@ -5949,41 +7541,51 @@ Reason: "%2" SampleTrack - Sample track + + Volume - Volume + + Panning - Panning + + + Sample track SampleTrackView + Track volume + Channel volume: + VOL + Panning + Panning: + PAN @@ -5991,482 +7593,661 @@ Reason: "%2" SetupDialog + Setup LMMS + + General settings + BUFFER SIZE + + Reset to default-value + MISC + Enable tooltips + Show restart warning after changing settings + Display volume as dBFS + Compress project files per default + One instrument track window mode + HQ-mode for output audio-device + Compact track buttons + Sync VST plugins to host playback + Enable note labels in piano roll + Enable waveform display by default + Keep effects running even without input + Create backup file when saving a project + + Reopen last project on start + + + + LANGUAGE + + Paths + + Directories + + + + LMMS working directory - VST-plugin directory + + Themes directory + Background artwork + + VST-plugin directory + + + + + GIG directory + + + + + SF2 directory + + + + + LADSPA plugin directories + + + + STK rawwave directory + Default Soundfont File + + Performance settings - UI effects vs. performance + + Auto save - Smooth scroll in Song Editor + + Enable auto save feature - Enable auto save feature + + UI effects vs. performance + + + + + Smooth scroll in Song Editor + Show playback cursor in AudioFileProcessor + + Audio settings + AUDIO INTERFACE + + MIDI settings + MIDI INTERFACE + OK + Cancel + Restart LMMS + Please note that most changes won't take effect until you restart LMMS! + Frames: %1 Latency: %2 ms + Here you can setup the internal buffer-size used by LMMS. Smaller values result in a lower latency but also may cause unusable sound or bad performance, especially on older computers or systems with a non-realtime kernel. + Choose LMMS working directory - Choose your VST-plugin directory - - - - Choose artwork-theme directory - - - - Choose LADSPA plugin directory - - - - Choose STK rawwave directory - - - - Choose default SoundFont + + Choose your GIG directory - Choose background artwork + + Choose your SF2 directory - Here you can select your preferred audio-interface. Depending on the configuration of your system during compilation time you can choose between ALSA, JACK, OSS and more. Below you see a box which offers controls to setup the selected audio-interface. + + Choose your VST-plugin directory - Here you can select your preferred MIDI-interface. Depending on the configuration of your system during compilation time you can choose between ALSA, OSS and more. Below you see a box which offers controls to setup the selected MIDI-interface. + + Choose artwork-theme directory - Reopen last project on start + + Choose LADSPA plugin directory - Directories + + Choose STK rawwave directory - Themes directory + + Choose default SoundFont - GIG directory + + Choose background artwork - SF2 directory + + minutes - LADSPA plugin directories + + minute - Auto save + + Auto save interval: %1 %2 - Choose your GIG directory + + Set the time between automatic backup to %1. +Remember to also save your project manually. - Choose your SF2 directory + + Here you can select your preferred audio-interface. Depending on the configuration of your system during compilation time you can choose between ALSA, JACK, OSS and more. Below you see a box which offers controls to setup the selected audio-interface. - minutes + + Here you can select your preferred MIDI-interface. Depending on the configuration of your system during compilation time you can choose between ALSA, OSS and more. Below you see a box which offers controls to setup the selected MIDI-interface. + + + SmfImport - minute + + + Setup incomplete - Auto save interval: %1 %2 + + You do not have set up a default soundfont in the settings dialog (Edit->Settings). Therefore no sound will be played back after importing this MIDI file. You should download a General MIDI soundfont, specify it in settings dialog and try again. - Set the time between automatic backup to %1. -Remember to also save your project manually. + + You did not compile LMMS with support for SoundFont2 player, which is used to add default sound to imported MIDI files. Therefore no sound will be played back after importing this MIDI file. Song + Tempo + Master volume + Master pitch + + LMMS Error report + + + + Project saved + The project %1 is now saved. + Project NOT saved. + The project %1 was not saved! + Import file + MIDI sequences + + Overture projects + + + + + Cakewalk projects + + + + Hydrogen projects + All file types + + Empty project + + This project is empty so exporting makes no sense. Please put some items into Song Editor first! + Select directory for writing exported tracks... + + untitled + + Select file for project-export... - The following errors occured while loading: + + Save project + + + + + Hint of MIDI export + + + + + To export midi from LMMS, Please ensure the following things. + +1. Instrument of tracks to export should be set to Sf2 Player. +2. Bassline or Beats will not be exported. +3. 16 tracks or less will be better. + +Continue? + MIDI File (*.mid) - LMMS Error report + + The following errors occured while loading: SongEditor + Could not open file - Could not write file + + Could not open file %1. You probably have no permissions to read this file. + Please make sure to have at least read permissions to the file and try again. - Could not open file %1. You probably have no permissions to read this file. - Please make sure to have at least read permissions to the file and try again. + + Could not write file - Error in file + + Could not open %1 for writing. You probably are not permitted to write to this file. Please make sure you have write-access to the file and try again. - The file %1 seems to contain errors and therefore can't be loaded. + + Error in file - Tempo + + The file %1 seems to contain errors and therefore can't be loaded. - TEMPO/BPM + + Version difference - tempo of song + + This %1 was created with LMMS %2. - The tempo of a song is specified in beats per minute (BPM). If you want to change the tempo of your song, change this value. Every measure has four beats, so the tempo in BPM specifies, how many measures / 4 should be played within a minute (or how many measures should be played within four minutes). + + template - High quality mode + + project - Master volume + + Tempo - master volume + + TEMPO/BPM - Master pitch + + tempo of song - master pitch + + The tempo of a song is specified in beats per minute (BPM). If you want to change the tempo of your song, change this value. Every measure has four beats, so the tempo in BPM specifies, how many measures / 4 should be played within a minute (or how many measures should be played within four minutes). - Value: %1% + + High quality mode - Value: %1 semitones + + + Master volume - Could not open %1 for writing. You probably are not permitted to write to this file. Please make sure you have write-access to the file and try again. + + master volume - template + + + Master pitch - project + + master pitch - Version difference + + Value: %1% - This %1 was created with LMMS %2. + + Value: %1 semitones SongEditorWindow + Song-Editor + Play song (Space) + Record samples from Audio-device + Record samples from Audio-device while playing song or BB track + Stop song (Space) - Add beat/bassline + + Click here, if you want to play your whole song. Playing will be started at the song-position-marker (green). You can also move it while playing. - Add sample-track + + Click here, if you want to stop playing of your song. The song-position-marker will be set to the start of your song. - Add automation-track + + Track actions - Draw mode + + Add beat/bassline - Edit mode (select and move) + + Add sample-track - Click here, if you want to play your whole song. Playing will be started at the song-position-marker (green). You can also move it while playing. + + Add automation-track - Click here, if you want to stop playing of your song. The song-position-marker will be set to the start of your song. + + Edit actions - Track actions + + Draw mode - Edit actions + + Edit mode (select and move) + Timeline controls + Zoom controls @@ -6474,10 +8255,12 @@ Remember to also save your project manually. SpectrumAnalyzerControlDialog + Linear spectrum + Linear Y axis @@ -6485,14 +8268,17 @@ Remember to also save your project manually. SpectrumAnalyzerControls + Linear spectrum + Linear Y axis + Channel mode @@ -6500,14 +8286,17 @@ Remember to also save your project manually. SubWindow + Close + Maximize + Restore @@ -6515,6 +8304,8 @@ Remember to also save your project manually. TabWidget + + Settings for %1 @@ -6522,74 +8313,93 @@ Remember to also save your project manually. TempoSyncKnob + + Tempo Sync + No Sync + Eight beats + Whole note + Half note + Quarter note + 8th note + 16th note + 32nd note + Custom... + Custom + Synced to Eight Beats + Synced to Whole Note + Synced to Half Note + Synced to Quarter Note + Synced to 8th Note + Synced to 16th Note + Synced to 32nd Note @@ -6597,30 +8407,37 @@ Remember to also save your project manually. TimeDisplayWidget + click to change time units + MIN + SEC + MSEC + BAR + BEAT + TICK @@ -6628,34 +8445,43 @@ Remember to also save your project manually. TimeLineWidget + Enable/disable auto-scrolling + Enable/disable loop-points + After stopping go back to begin + After stopping go back to position at which playing was started + After stopping keep position + + Hint + Press <%1> to disable magnetic loop points. + Hold <Shift> to move the begin loop point; Press <%1> to disable magnetic loop points. @@ -6663,10 +8489,12 @@ Remember to also save your project manually. Track + Mute + Solo @@ -6674,43 +8502,60 @@ Remember to also save your project manually. TrackContainer + + + Please wait... + + + + + + + Track %1 + + + + Couldn't import file + Couldn't find a filter for importing file %1. You should convert this file into a format supported by LMMS using another software. + Couldn't open file + Couldn't open file %1 for reading. Please make sure you have read-permission to the file and the directory containing the file and try again! - Loading project... - - - - Cancel + + Track - Please wait... + + Loading project... - Importing MIDI-file... + + Cancel TrackContentObject + Mute @@ -6718,46 +8563,58 @@ Please make sure you have read-permission to the file and the directory containi TrackContentObjectView + Current position + + Hint + Press <%1> and drag to make a copy. + Current length + Press <%1> for free resizing. + %1:%2 (%3:%4 to %5:%6) + Delete (middle mousebutton) + Cut + Copy + Paste + Mute/unmute (<%1> + middle click) @@ -6765,193 +8622,243 @@ Please make sure you have read-permission to the file and the directory containi TrackOperationsWidget + Press <%1> while clicking on move-grip to begin a new drag'n'drop-action. + Actions for this track + Mute + + Solo + Mute this track + Clone this track + Remove this track + Clear this track + FX %1: %2 - Turn all recording on + + Assign to new FX Channel - Turn all recording off + + Turn all recording on - Assign to new FX Channel + + Turn all recording off TripleOscillatorView + Use phase modulation for modulating oscillator 1 with oscillator 2 + Use amplitude modulation for modulating oscillator 1 with oscillator 2 + Mix output of oscillator 1 & 2 + Synchronize oscillator 1 with oscillator 2 + Use frequency modulation for modulating oscillator 1 with oscillator 2 + Use phase modulation for modulating oscillator 2 with oscillator 3 + Use amplitude modulation for modulating oscillator 2 with oscillator 3 + Mix output of oscillator 2 & 3 + Synchronize oscillator 2 with oscillator 3 + Use frequency modulation for modulating oscillator 2 with oscillator 3 + Osc %1 volume: + With this knob you can set the volume of oscillator %1. When setting a value of 0 the oscillator is turned off. Otherwise you can hear the oscillator as loud as you set it here. + Osc %1 panning: + With this knob you can set the panning of the oscillator %1. A value of -100 means 100% left and a value of 100 moves oscillator-output right. + Osc %1 coarse detuning: + semitones + With this knob you can set the coarse detuning of oscillator %1. You can detune the oscillator 24 semitones (2 octaves) up and down. This is useful for creating sounds with a chord. + Osc %1 fine detuning left: + + cents + With this knob you can set the fine detuning of oscillator %1 for the left channel. The fine-detuning is ranged between -100 cents and +100 cents. This is useful for creating "fat" sounds. + Osc %1 fine detuning right: + With this knob you can set the fine detuning of oscillator %1 for the right channel. The fine-detuning is ranged between -100 cents and +100 cents. This is useful for creating "fat" sounds. + Osc %1 phase-offset: + + degrees + With this knob you can set the phase-offset of oscillator %1. That means you can move the point within an oscillation where the oscillator begins to oscillate. For example if you have a sine-wave and have a phase-offset of 180 degrees the wave will first go down. It's the same with a square-wave. + Osc %1 stereo phase-detuning: + With this knob you can set the stereo phase-detuning of oscillator %1. The stereo phase-detuning specifies the size of the difference between the phase-offset of left and right channel. This is very good for creating wide stereo sounds. + Use a sine-wave for current oscillator. + Use a triangle-wave for current oscillator. + Use a saw-wave for current oscillator. + Use a square-wave for current oscillator. + Use a moog-like saw-wave for current oscillator. + Use an exponential wave for current oscillator. + Use white-noise for current oscillator. + Use a user-defined waveform for current oscillator. @@ -6959,101 +8866,131 @@ Please make sure you have read-permission to the file and the directory containi VersionedSaveDialog + Increment version number + Decrement version number + + + already exists. Do you want to replace it? + + VestigeInstrumentView + Open other VST-plugin + Click here, if you want to open another VST-plugin. After clicking on this button, a file-open-dialog appears and you can select your file. - Show/hide GUI + + Control VST-plugin from LMMS host - Click here to show or hide the graphical user interface (GUI) of your VST-plugin. + + Click here, if you want to control VST-plugin from host. - Turn off all notes + + Open VST-plugin preset - Open VST-plugin + + Click here, if you want to open another *.fxp, *.fxb VST-plugin preset. - DLL-files (*.dll) + + Previous (-) - EXE-files (*.exe) + + + Click here, if you want to switch to another VST-plugin preset program. - No VST-plugin loaded + + Save preset - Control VST-plugin from LMMS host + + Click here, if you want to save current VST-plugin preset program. - Click here, if you want to control VST-plugin from host. + + Next (+) - Open VST-plugin preset + + Click here to select presets that are currently loaded in VST. - Click here, if you want to open another *.fxp, *.fxb VST-plugin preset. + + Show/hide GUI - Previous (-) + + Click here to show or hide the graphical user interface (GUI) of your VST-plugin. - Click here, if you want to switch to another VST-plugin preset program. + + Turn off all notes - Save preset + + Open VST-plugin - Click here, if you want to save current VST-plugin preset program. + + DLL-files (*.dll) - Next (+) + + EXE-files (*.exe) - Click here to select presets that are currently loaded in VST. + + No VST-plugin loaded + Preset + by + - VST plugin control @@ -7061,10 +8998,12 @@ Please make sure you have read-permission to the file and the directory containi VisualizationWidget + click to enable/disable visualization of master-output + Click to enable @@ -7072,54 +9011,69 @@ Please make sure you have read-permission to the file and the directory containi VstEffectControlDialog + Show/hide + Control VST-plugin from LMMS host + Click here, if you want to control VST-plugin from host. + Open VST-plugin preset + Click here, if you want to open another *.fxp, *.fxb VST-plugin preset. + Previous (-) + + Click here, if you want to switch to another VST-plugin preset program. + Next (+) + Click here to select presets that are currently loaded in VST. + Save preset + Click here, if you want to save current VST-plugin preset program. + + Effect by: + &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br /> @@ -7127,173 +9081,217 @@ Please make sure you have read-permission to the file and the directory containi VstPlugin - Loading plugin + + + The VST plugin %1 could not be loaded. + Open Preset + + Vst Plugin Preset (*.fxp *.fxb) + : default + " + ' + Save Preset + .fxp + .FXP + .FXB + .fxb - Please wait while loading VST plugin... + + Loading plugin - The VST plugin %1 could not be loaded. + + Please wait while loading VST plugin... WatsynInstrument + Volume A1 + Volume A2 + Volume B1 + Volume B2 + Panning A1 + Panning A2 + Panning B1 + Panning B2 + Freq. multiplier A1 + Freq. multiplier A2 + Freq. multiplier B1 + Freq. multiplier B2 + Left detune A1 + Left detune A2 + Left detune B1 + Left detune B2 + Right detune A1 + Right detune A2 + Right detune B1 + Right detune B2 + A-B Mix + A-B Mix envelope amount + A-B Mix envelope attack + A-B Mix envelope hold + A-B Mix envelope decay + A1-B2 Crosstalk + A2-A1 modulation + B2-B1 modulation + Selected graph @@ -7301,213 +9299,291 @@ Please make sure you have read-permission to the file and the directory containi WatsynView - Select oscillator A1 + + + + + Volume - Select oscillator A2 + + + + + Panning - Select oscillator B1 + + + + + Freq. multiplier - Select oscillator B2 + + + + + Left detune - Mix output of A2 to A1 + + + + + + + + + cents - Modulate amplitude of A1 with output of A2 + + + + + Right detune - Ring-modulate A1 and A2 + + A-B Mix - Modulate phase of A1 with output of A2 + + Mix envelope amount - Mix output of B2 to B1 + + Mix envelope attack - Modulate amplitude of B1 with output of B2 + + Mix envelope hold - Ring-modulate B1 and B2 + + Mix envelope decay - Modulate phase of B1 with output of B2 + + Crosstalk - Draw your own waveform here by dragging your mouse on this graph. + + Select oscillator A1 - Load waveform + + Select oscillator A2 - Click to load a waveform from a sample file + + Select oscillator B1 - Phase left + + Select oscillator B2 - Click to shift phase by -15 degrees + + Mix output of A2 to A1 - Phase right + + Modulate amplitude of A1 with output of A2 - Click to shift phase by +15 degrees + + Ring-modulate A1 and A2 - Normalize + + Modulate phase of A1 with output of A2 - Click to normalize + + Mix output of B2 to B1 - Invert + + Modulate amplitude of B1 with output of B2 - Click to invert + + Ring-modulate B1 and B2 - Smooth + + Modulate phase of B1 with output of B2 - Click to smooth + + + + + Draw your own waveform here by dragging your mouse on this graph. - Sine wave + + Load waveform - Click for sine wave + + Click to load a waveform from a sample file - Triangle wave + + Phase left - Click for triangle wave + + Click to shift phase by -15 degrees - Click for saw wave + + Phase right - Square wave + + Click to shift phase by +15 degrees - Click for square wave + + Normalize - Volume + + Click to normalize - Panning + + Invert - Freq. multiplier + + Click to invert - Left detune + + Smooth - cents + + Click to smooth - Right detune + + Sine wave - A-B Mix + + Click for sine wave - Mix envelope amount + + + Triangle wave - Mix envelope attack + + Click for triangle wave - Mix envelope hold + + Click for saw wave - Mix envelope decay + + Square wave - Crosstalk + + Click for square wave ZynAddSubFxInstrument + Portamento + Filter Frequency + Filter Resonance + Bandwidth + FM Gain + Resonance Center Frequency + Resonance Bandwidth + Forward MIDI Control Change Events @@ -7515,121 +9591,150 @@ Please make sure you have read-permission to the file and the directory containi ZynAddSubFxView - Show GUI - - - - Click here to show or hide the graphical user interface (GUI) of ZynAddSubFX. - - - + Portamento: + PORT + Filter Frequency: + FREQ + Filter Resonance: + RES + Bandwidth: + BW + FM Gain: + FM GAIN + Resonance center frequency: + RES CF + Resonance bandwidth: + RES BW + Forward MIDI Control Changes + + + Show GUI + + + + + Click here to show or hide the graphical user interface (GUI) of ZynAddSubFX. + + audioFileProcessor + Amplify + Start of sample + End of sample - Reverse sample + + Loopback point - Stutter + + Reverse sample - Loopback point + + Loop mode - Loop mode + + Stutter + Interpolation mode + None + Linear + Sinc + Sample not found: %1 @@ -7637,6 +9742,7 @@ Please make sure you have read-permission to the file and the directory containi bitInvader + Samplelength @@ -7644,165 +9750,215 @@ Please make sure you have read-permission to the file and the directory containi bitInvaderView + Sample Length + + Draw your own waveform here by dragging your mouse on this graph. + + + + Sine wave - Triangle wave + + Click for a sine-wave. - Saw wave + + Triangle wave - Square wave + + Click here for a triangle-wave. - White noise wave + + Saw wave - User defined wave + + Click here for a saw-wave. - Smooth + + Square wave - Click here to smooth waveform. + + Click here for a square-wave. - Interpolation + + White noise wave - Normalize + + Click here for white-noise. - Draw your own waveform here by dragging your mouse on this graph. + + User defined wave - Click for a sine-wave. + + Click here for a user-defined shape. - Click here for a triangle-wave. + + Smooth - Click here for a saw-wave. + + Click here to smooth waveform. - Click here for a square-wave. + + Interpolation - Click here for white-noise. + + Normalize + + + commonReader - Click here for a user-defined shape. + + + + Importing %1 file... dynProcControlDialog + INPUT + Input gain: + OUTPUT + Output gain: + ATTACK + Peak attack time: + RELEASE + Peak release time: + Reset waveform + Click here to reset the wavegraph back to default + Smooth waveform + Click here to apply smoothing to wavegraph + Increase wavegraph amplitude by 1dB + Click here to increase wavegraph amplitude by 1dB + Decrease wavegraph amplitude by 1dB + Click here to decrease wavegraph amplitude by 1dB + Stereomode Maximum + Process based on the maximum of both stereo channels + Stereomode Average + Process based on the average of both stereo channels + Stereomode Unlinked + Process each stereo channel independently @@ -7810,22 +9966,27 @@ Please make sure you have read-permission to the file and the directory containi dynProcControls + Input gain + Output gain + Attack time + Release time + Stereo mode @@ -7833,10 +9994,12 @@ Please make sure you have read-permission to the file and the directory containi fxLineLcdSpinBox + Assign to: + New FX Channel @@ -7844,6 +10007,7 @@ Please make sure you have read-permission to the file and the directory containi graphModel + Graph @@ -7851,50 +10015,62 @@ Please make sure you have read-permission to the file and the directory containi kickerInstrument + Start frequency + End frequency - Gain - - - + Length + Distortion Start + Distortion End + + Gain + + + + Envelope Slope + Noise + Click + Frequency Slope + Start from note + End to note @@ -7902,42 +10078,52 @@ Please make sure you have read-permission to the file and the directory containi kickerInstrumentView + Start frequency: + End frequency: - Gain: + + Frequency Slope: - Frequency Slope: + + Gain: + Envelope Length: + Envelope Slope: + Click: + Noise: + Distortion Start: + Distortion End: @@ -7945,26 +10131,37 @@ Please make sure you have read-permission to the file and the directory containi ladspaBrowserView + + Available Effects + + Unavailable Effects + + Instruments + + Analysis Tools + + Don't know + This dialog displays information on all of the LADSPA plugins LMMS was able to locate. The plugins are divided into five categories based upon an interpretation of the port types and names. Available Effects are those that can be used by LMMS. In order for LMMS to be able to use an effect, it must, first and foremost, be an effect, which is to say, it has to have both input channels and output channels. LMMS identifies an input channel as an audio rate port containing 'in' in the name. Output channels are identified by the letters 'out'. Furthermore, the effect must have the same number of inputs and outputs and be real time capable. @@ -7981,6 +10178,7 @@ Double clicking any of the plugins will bring up information on the ports. + Type: @@ -7988,10 +10186,12 @@ Double clicking any of the plugins will bring up information on the ports. ladspaDescription + Plugins + Description @@ -7999,66 +10199,83 @@ Double clicking any of the plugins will bring up information on the ports. ladspaPortDialog + Ports + Name + Rate + Direction + Type + Min < Default < Max + Logarithmic + SR Dependent + Audio + Control + Input + Output + Toggled + Integer + Float + + Yes @@ -8066,46 +10283,57 @@ Double clicking any of the plugins will bring up information on the ports. lb302Synth + VCF Cutoff Frequency + VCF Resonance + VCF Envelope Mod + VCF Envelope Decay + Distortion + Waveform + Slide Decay + Slide + Accent + Dead + 24dB/oct Filter @@ -8113,122 +10341,153 @@ Double clicking any of the plugins will bring up information on the ports. lb302SynthView + Cutoff Freq: + Resonance: + Env Mod: + Decay: + 303-es-que, 24dB/octave, 3 pole filter + Slide Decay: + DIST: + Saw wave + Click here for a saw-wave. + Triangle wave + Click here for a triangle-wave. + Square wave + Click here for a square-wave. + Rounded square wave + Click here for a square-wave with a rounded end. + Moog wave + Click here for a moog-like wave. + Sine wave + Click for a sine-wave. + + White noise wave + Click here for an exponential wave. + Click here for white-noise. + Bandlimited saw wave + Click here for bandlimited saw wave. + Bandlimited square wave + Click here for bandlimited square wave. + Bandlimited triangle wave + Click here for bandlimited triangle wave. + Bandlimited moog saw wave + Click here for bandlimited moog saw wave. @@ -8236,118 +10495,147 @@ Double clicking any of the plugins will bring up information on the ports. malletsInstrument + Hardness + Position + Vibrato Gain + Vibrato Freq + Stick Mix + Modulator + Crossfade + LFO Speed + LFO Depth + ADSR + Pressure + Motion + Speed + Bowed + Spread + Marimba + Vibraphone + Agogo + Wood1 + Reso + Wood2 + Beats + Two Fixed + Clump + Tubular Bells + Uniform Bar + Tuned Bar + Glass + Tibetan Bowl @@ -8355,149 +10643,186 @@ Double clicking any of the plugins will bring up information on the ports. malletsInstrumentView + Instrument + Spread + Spread: + + Missing files + + + + + Your Stk-installation seems to be incomplete. Please make sure the full Stk-package is installed! + + + + Hardness + Hardness: + Position + Position: + Vib Gain + Vib Gain: + Vib Freq + Vib Freq: + Stick Mix + Stick Mix: + Modulator + Modulator: + Crossfade + Crossfade: + LFO Speed + LFO Speed: + LFO Depth + LFO Depth: + ADSR + ADSR: + Pressure + Pressure: + Speed + Speed: - - Missing files - - - - Your Stk-installation seems to be incomplete. Please make sure the full Stk-package is installed! - - manageVSTEffectView + - VST parameter control + VST Sync + Click here if you want to synchronize all parameters with VST plugin. + + Automated + Click here if you want to display automated parameters only. + Close + Close VST effect knob-controller window. @@ -8505,30 +10830,39 @@ Double clicking any of the plugins will bring up information on the ports. manageVestigeInstrumentView + + - VST plugin control + VST Sync + Click here if you want to synchronize all parameters with VST plugin. + + Automated + Click here if you want to display automated parameters only. + Close + Close VST plugin knob-controller window. @@ -8536,118 +10870,147 @@ Double clicking any of the plugins will bring up information on the ports. opl2instrument + Patch + Op 1 Attack + Op 1 Decay + Op 1 Sustain + Op 1 Release + Op 1 Level + Op 1 Level Scaling + Op 1 Frequency Multiple + Op 1 Feedback + Op 1 Key Scaling Rate + Op 1 Percussive Envelope + Op 1 Tremolo + Op 1 Vibrato + Op 1 Waveform + Op 2 Attack + Op 2 Decay + Op 2 Sustain + Op 2 Release + Op 2 Level + Op 2 Level Scaling + Op 2 Frequency Multiple + Op 2 Key Scaling Rate + Op 2 Percussive Envelope + Op 2 Tremolo + Op 2 Vibrato + Op 2 Waveform + FM + Vibrato Depth + Tremolo Depth @@ -8655,18 +11018,26 @@ Double clicking any of the plugins will bring up information on the ports. opl2instrumentView + + Attack + + Decay + + Release + + Frequency multiplier @@ -8674,10 +11045,12 @@ Double clicking any of the plugins will bring up information on the ports. organicInstrument + Distortion + Volume @@ -8685,50 +11058,63 @@ Double clicking any of the plugins will bring up information on the ports. organicInstrumentView + Distortion: - Volume: + + The distortion knob adds distortion to the output of the instrument. - Randomise + + Volume: - Osc %1 waveform: + + The volume knob controls the volume of the output of the instrument. It is cumulative with the instrument window's volume control. - Osc %1 volume: + + Randomise - Osc %1 panning: + + The randomize button randomizes all knobs except the harmonics,main volume and distortion knobs. - cents + + + Osc %1 waveform: - The distortion knob adds distortion to the output of the instrument. + + Osc %1 volume: - The volume knob controls the volume of the output of the instrument. It is cumulative with the instrument window's volume control. + + Osc %1 panning: - The randomize button randomizes all knobs except the harmonics,main volume and distortion knobs. + + Osc %1 stereo detuning - Osc %1 stereo detuning + + cents + Osc %1 harmonic: @@ -8736,265 +11122,351 @@ Double clicking any of the plugins will bring up information on the ports. papuInstrument + Sweep time + Sweep direction + Sweep RtShift amount + + Wave Pattern Duty + Channel 1 volume + + + Volume sweep direction + + + Length of each step in sweep + Channel 2 volume + Channel 3 volume + Channel 4 volume + + Shift Register width + + + + Right Output level + Left Output level + Channel 1 to SO2 (Left) + Channel 2 to SO2 (Left) + Channel 3 to SO2 (Left) + Channel 4 to SO2 (Left) + Channel 1 to SO1 (Right) + Channel 2 to SO1 (Right) + Channel 3 to SO1 (Right) + Channel 4 to SO1 (Right) + Treble + Bass - - Shift Register width - - papuInstrumentView + Sweep Time: + Sweep Time + + The amount of increase or decrease in frequency + + + + Sweep RtShift amount: + Sweep RtShift amount + + The rate at which increase or decrease in frequency occurs + + + + + Wave pattern duty: + Wave Pattern Duty + + + The duty cycle is the ratio of the duration (time) that a signal is ON versus the total period of the signal. + + + + + Square Channel 1 Volume: + + Square Channel 1 Volume + + + + + + Length of each step in sweep: + + + Length of each step in sweep + + + + The delay between step change + + + + Wave pattern duty + Square Channel 2 Volume: + + Square Channel 2 Volume + Wave Channel Volume: + + Wave Channel Volume + Noise Channel Volume: + + Noise Channel Volume + SO1 Volume (Right): + SO1 Volume (Right) + SO2 Volume (Left): + SO2 Volume (Left) + Treble: + Treble + Bass: + Bass + Sweep Direction + + + + + Volume Sweep Direction + Shift Register Width + Channel1 to SO1 (Right) + Channel2 to SO1 (Right) + Channel3 to SO1 (Right) + Channel4 to SO1 (Right) + Channel1 to SO2 (Left) + Channel2 to SO2 (Left) + Channel3 to SO2 (Left) + Channel4 to SO2 (Left) + Wave Pattern - The amount of increase or decrease in frequency - - - - The rate at which increase or decrease in frequency occurs - - - - The duty cycle is the ratio of the duration (time) that a signal is ON versus the total period of the signal. - - - - Square Channel 1 Volume - - - - The delay between step change - - - + Draw the wave here @@ -9002,34 +11474,42 @@ Double clicking any of the plugins will bring up information on the ports. patchesDialog + Qsynth: Channel Preset + Bank selector + Bank + Program selector + Patch + Name + OK + Cancel @@ -9037,238 +11517,296 @@ Double clicking any of the plugins will bring up information on the ports. pluginBrowser - no description + + A native amplifier plugin - Incomplete monophonic imitation tb303 + + Simple sampler with various settings for using samples (e.g. drums) in an instrument-track - Plugin for freely manipulating stereo output + + Boost your bass the fast and simple way - Plugin for controlling knobs with sound peaks + + Customizable wavetable synthesizer - Plugin for enhancing stereo separation of a stereo input file + + An oversampling bitcrusher - List installed LADSPA plugins + + Carla Patchbay Instrument - GUS-compatible patch instrument + + Carla Rack Instrument - Additive Synthesizer for organ-like sounds + + A 4-band Crossover Equalizer - Tuneful things to bang on + + A native delay plugin - VST-host for using VST(i)-plugins within LMMS + + A Dual filter plugin - Vibrating string modeler + + plugin for processing dynamics in a flexible way - plugin for using arbitrary LADSPA-effects inside LMMS. + + A native eq plugin - Filter for importing MIDI-files into LMMS + + A native flanger plugin - Emulation of the MOS6581 and MOS8580 SID. -This chip was used in the Commodore 64 computer. + + Player for GIG files - Player for SoundFont files + + Filter for importing Hydrogen files into LMMS - Emulation of GameBoy (TM) APU + + Versatile drum synthesizer - Customizable wavetable synthesizer + + List installed LADSPA plugins - Embedded ZynAddSubFX + + plugin for using arbitrary LADSPA-effects inside LMMS. - 2-operator FM Synth + + Incomplete monophonic imitation tb303 - Filter for importing Hydrogen files into LMMS + + Filter for exporting MIDI-files from LMMS - LMMS port of sfxr + + Monstrous 3-oscillator synth with modulation matrix - Monstrous 3-oscillator synth with modulation matrix + + A multitap echo delay plugin - Three powerful oscillators you can modulate in several ways + + A NES-like synthesizer - A native amplifier plugin + + 2-operator FM Synth - Carla Rack Instrument + + Additive Synthesizer for organ-like sounds - 4-oscillator modulatable wavetable synth + + Emulation of GameBoy (TM) APU - plugin for waveshaping + + GUS-compatible patch instrument - Boost your bass the fast and simple way + + Plugin for controlling knobs with sound peaks - Versatile drum synthesizer + + Player for SoundFont files - Simple sampler with various settings for using samples (e.g. drums) in an instrument-track + + LMMS port of sfxr - plugin for processing dynamics in a flexible way + + Emulation of the MOS6581 and MOS8580 SID. +This chip was used in the Commodore 64 computer. - Carla Patchbay Instrument + + Filter for importing MIDI-like files into LMMS - plugin for using arbitrary VST effects inside LMMS. + + Graphical spectrum analyzer plugin - Graphical spectrum analyzer plugin + + Plugin for enhancing stereo separation of a stereo input file - A NES-like synthesizer + + Plugin for freely manipulating stereo output - A native delay plugin + + Tuneful things to bang on - Player for GIG files + + Three powerful oscillators you can modulate in several ways - A multitap echo delay plugin + + VST-host for using VST(i)-plugins within LMMS - A native flanger plugin + + Vibrating string modeler - An oversampling bitcrusher + + plugin for using arbitrary VST effects inside LMMS. - A native eq plugin + + 4-oscillator modulatable wavetable synth - A 4-band Crossover Equalizer + + plugin for waveshaping - A Dual filter plugin + + Embedded ZynAddSubFX - Filter for exporting MIDI-files from LMMS + + no description sf2Instrument + Bank + Patch + Gain + Reverb + Reverb Roomsize + Reverb Damping + Reverb Width + Reverb Level + Chorus + Chorus Lines + Chorus Level + Chorus Speed + Chorus Depth + A soundfont %1 could not be loaded. @@ -9276,74 +11814,92 @@ This chip was used in the Commodore 64 computer. sf2InstrumentView + Open other SoundFont file + Click here to open another SF2 file + Choose the patch + Gain + Apply reverb (if supported) + This button enables the reverb effect. This is useful for cool effects, but only works on files that support it. + Reverb Roomsize: + Reverb Damping: + Reverb Width: + Reverb Level: + Apply chorus (if supported) + This button enables the chorus effect. This is useful for cool echo effects, but only works on files that support it. + Chorus Lines: + Chorus Level: + Chorus Speed: + Chorus Depth: + Open SoundFont file + SoundFont2 Files (*.sf2) @@ -9351,6 +11907,7 @@ This chip was used in the Commodore 64 computer. sfxrInstrument + Wave Form @@ -9358,26 +11915,32 @@ This chip was used in the Commodore 64 computer. sidInstrument + Cutoff + Resonance + Filter type + Voice 3 off + Volume + Chip model @@ -9385,134 +11948,172 @@ This chip was used in the Commodore 64 computer. sidInstrumentView + Volume: + Resonance: + + Cutoff frequency: + High-Pass filter + Band-Pass filter + Low-Pass filter + Voice3 Off + MOS6581 SID + MOS8580 SID + + Attack: + Attack rate determines how rapidly the output of Voice %1 rises from zero to peak amplitude. + + Decay: + Decay rate determines how rapidly the output falls from the peak amplitude to the selected Sustain level. + Sustain: + Output of Voice %1 will remain at the selected Sustain amplitude as long as the note is held. + + Release: + The output of of Voice %1 will fall from Sustain amplitude to zero amplitude at the selected Release rate. + + Pulse Width: + The Pulse Width resolution allows the width to be smoothly swept with no discernable stepping. The Pulse waveform on Oscillator %1 must be selected to have any audible effect. + Coarse: + The Coarse detuning allows to detune Voice %1 one octave up or down. + Pulse Wave + Triangle Wave + SawTooth + Noise + Sync + Sync synchronizes the fundamental frequency of Oscillator %1 with the fundamental frequency of Oscillator %2 producing "Hard Sync" effects. + Ring-Mod + Ring-mod replaces the Triangle Waveform output of Oscillator %1 with a "Ring Modulated" combination of Oscillators %1 and %2. + Filtered + When Filtered is on, Voice %1 will be processed through the Filter. When Filtered is off, Voice %1 appears directly at the output, and the Filter has no effect on it. + Test + Test, when set, resets and locks Oscillator %1 at zero until Test is turned off. @@ -9520,10 +12121,12 @@ This chip was used in the Commodore 64 computer. stereoEnhancerControlDialog + WIDE + Width: @@ -9531,6 +12134,7 @@ This chip was used in the Commodore 64 computer. stereoEnhancerControls + Width @@ -9538,18 +12142,22 @@ This chip was used in the Commodore 64 computer. stereoMatrixControlDialog + Left to Left Vol: + Left to Right Vol: + Right to Left Vol: + Right to Right Vol: @@ -9557,18 +12165,22 @@ This chip was used in the Commodore 64 computer. stereoMatrixControls + Left to Left + Left to Right + Right to Left + Right to Right @@ -9576,10 +12188,12 @@ This chip was used in the Commodore 64 computer. vestigeInstrument + Loading plugin + Please wait while loading VST-plugin... @@ -9587,42 +12201,52 @@ This chip was used in the Commodore 64 computer. vibed + String %1 volume + String %1 stiffness + Pick %1 position + Pickup %1 position + Pan %1 + Detune %1 + Fuzziness %1 + Length %1 + Impulse %1 + Octave %1 @@ -9630,90 +12254,112 @@ This chip was used in the Commodore 64 computer. vibedView + Volume: + The 'V' knob sets the volume of the selected string. + String stiffness: + The 'S' knob sets the stiffness of the selected string. The stiffness of the string affects how long the string will ring out. The lower the setting, the longer the string will ring. + Pick position: + The 'P' knob sets the position where the selected string will be 'picked'. The lower the setting the closer the pick is to the bridge. + Pickup position: + The 'PU' knob sets the position where the vibrations will be monitored for the selected string. The lower the setting, the closer the pickup is to the bridge. + Pan: + The Pan knob determines the location of the selected string in the stereo field. + Detune: + The Detune knob modifies the pitch of the selected string. Settings less than zero will cause the string to sound flat. Settings greater than zero will cause the string to sound sharp. + Fuzziness: + The Slap knob adds a bit of fuzz to the selected string which is most apparent during the attack, though it can also be used to make the string sound more 'metallic'. + Length: + The Length knob sets the length of the selected string. Longer strings will both ring longer and sound brighter, however, they will also eat up more CPU cycles. + Impulse or initial state + The 'Imp' selector determines whether the waveform in the graph is to be treated as an impulse imparted to the string by the pick or the initial state of the string. + Octave + The Octave selector is used to choose which harmonic of the note the string will ring at. For example, '-2' means the string will ring two octaves below the fundamental, 'F' means the string will ring at the fundamental, and '6' means the string will ring six octaves above the fundamental. + Impulse Editor + The waveform editor provides control over the initial state or impulse that is used to start the string vibrating. The buttons to the right of the graph will initialize the waveform to the selected type. The '?' button will load a waveform from a file--only the first 128 samples will be loaded. The waveform can also be drawn in the graph. @@ -9724,6 +12370,7 @@ The 'N' button will normalize the waveform. + Vibed models up to nine independently vibrating strings. The 'String' selector allows you to choose which string is being edited. The 'Imp' selector chooses whether the graph represents an impulse or the initial state of the string. The 'Octave' selector chooses which harmonic the string should vibrate at. The graph allows you to control the initial state or impulse used to set the string in motion. @@ -9738,129 +12385,160 @@ The LED in the lower right corner of the waveform editor determines whether the + Enable waveform + Click here to enable/disable waveform. + String + The String selector is used to choose which string the controls are editing. A Vibed instrument can contain up to nine independently vibrating strings. The LED in the lower right corner of the waveform editor indicates whether the selected string is active. + Sine wave - Triangle wave + + Use a sine-wave for current oscillator. - Saw wave + + Triangle wave - Square wave + + Use a triangle-wave for current oscillator. - White noise wave + + Saw wave - User defined wave + + Use a saw-wave for current oscillator. - Smooth + + Square wave - Click here to smooth waveform. + + Use a square-wave for current oscillator. - Normalize + + White noise wave - Click here to normalize waveform. + + Use white-noise for current oscillator. - Use a sine-wave for current oscillator. + + User defined wave - Use a triangle-wave for current oscillator. + + Use a user-defined waveform for current oscillator. - Use a saw-wave for current oscillator. + + Smooth - Use a square-wave for current oscillator. + + Click here to smooth waveform. - Use white-noise for current oscillator. + + Normalize - Use a user-defined waveform for current oscillator. + + Click here to normalize waveform. voiceObject + Voice %1 pulse width + Voice %1 attack + Voice %1 decay + Voice %1 sustain + Voice %1 release + Voice %1 coarse detuning + Voice %1 wave shape + Voice %1 sync + Voice %1 ring modulate + Voice %1 filtered + Voice %1 test @@ -9868,58 +12546,72 @@ The LED in the lower right corner of the waveform editor determines whether the waveShaperControlDialog + INPUT + Input gain: + OUTPUT + Output gain: + Reset waveform + Click here to reset the wavegraph back to default + Smooth waveform + Click here to apply smoothing to wavegraph + Increase graph amplitude by 1dB + Click here to increase wavegraph amplitude by 1dB + Decrease graph amplitude by 1dB + Click here to decrease wavegraph amplitude by 1dB + Clip input + Clip input signal to 0dB @@ -9927,10 +12619,12 @@ The LED in the lower right corner of the waveform editor determines whether the waveShaperControls + Input gain + Output gain diff --git a/include/SmfMidiCC.h b/include/SmfMidiCC.h new file mode 100644 index 00000000000..6f8bc398c24 --- /dev/null +++ b/include/SmfMidiCC.h @@ -0,0 +1,51 @@ +/* + * smfMidiCC.h - support for importing MIDI files + * + * Copyright (c) 2005-2014 Tobias Doerffel + * + * This file is part of LMMS - http://lmms.io + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program (see COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + */ + +#ifndef SMF_MIDI_CC_H +#define SMF_MIDI_CC_H + +#include + +#include "export.h" +#include "TrackContainer.h" +#include "AutomationTrack.h" +#include "AutomationPattern.h" +#include "MidiTime.h" + +class EXPORT SmfMidiCC +{ + +public: + SmfMidiCC(); + + AutomationTrack * at; + AutomationPattern * ap; + MidiTime lastPos; + + SmfMidiCC & create(TrackContainer* tc, QString tn ); + SmfMidiCC & putValue( MidiTime time, AutomatableModel * objModel, float value ); + void clear(); +}; + +#endif diff --git a/include/SmfMidiChannel.h b/include/SmfMidiChannel.h new file mode 100644 index 00000000000..bdf39538272 --- /dev/null +++ b/include/SmfMidiChannel.h @@ -0,0 +1,57 @@ +/* + * smfMidiChannel.h - support for importing MIDI files + * + * Copyright (c) 2005-2014 Tobias Doerffel + * + * This file is part of LMMS - http://lmms.io + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program (see COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + */ + +#ifndef SMF_MIDI_CHANNEL_H +#define SMF_MIDI_CHANNEL_H + +#include + +#include "export.h" +#include "InstrumentTrack.h" +#include "Pattern.h" +#include "Instrument.h" +#include "MidiTime.h" + +class EXPORT SmfMidiChannel +{ + +public: + SmfMidiChannel(); + + InstrumentTrack * it; + Pattern* p; + Instrument * it_inst; + bool isSF2; + bool hasNotes; + MidiTime lastEnd; + QString trackName; + + SmfMidiChannel * create( TrackContainer* tc, QString tn ); + + void addNote( Note & n ); + void setName(QString tn ); + +}; + +#endif diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 943c8088c59..7bfcd86aebb 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -55,8 +55,8 @@ IF("${PLUGIN_LIST}" STREQUAL "") ladspa_browser LadspaEffect lb302 - MidiImport - # MidiExport - temporarily disabled, MIDI export is broken + SmfImport # For Midi, Overture and Cakewalk files. + MidiExport # temporarily disabled, MIDI export is broken MultitapEcho monstro nes diff --git a/plugins/MidiExport/CMakeLists.txt b/plugins/MidiExport/CMakeLists.txt index 1d19f081e6a..d2058c1db5a 100644 --- a/plugins/MidiExport/CMakeLists.txt +++ b/plugins/MidiExport/CMakeLists.txt @@ -1,4 +1,32 @@ -INCLUDE(BuildPlugin) +# check for drumstick-file -BUILD_PLUGIN(midiexport MidiExport.cpp MidiExport.h MidiFile.hpp - MOCFILES MidiExport.h) +IF(LMMS_HAVE_FLUIDSYNTH) + IF(WANT_QT5) + PKG_CHECK_MODULES(DRUMSTICK_FILE REQUIRED drumstick-file>=1.0.0) + ELSE() + PKG_CHECK_MODULES(DRUMSTICK_FILE REQUIRED drumstick-file>=0.5.0) + ENDIF() + IF(NOT DRUMSTICK_FILE_FOUND) + IF(WANT_QT5) + MESSAGE(FATAL_ERROR "LMMS requires libdrumstick-file and libdrumstick-dev >= 1.0.0 with Qt5 - please install, remove CMakeCache.txt and try again!") + ELSE() + MESSAGE(FATAL_ERROR "LMMS requires libdrumstick-file and libdrumstick-dev >= 0.5.0 with Qt4 - please install, remove CMakeCache.txt and try again!") + ENDIF() + ENDIF(NOT DRUMSTICK_FILE_FOUND) + INCLUDE(BuildPlugin) + LINK_DIRECTORIES(${DRUMSTICK_FILE_LIBDIR}) + INCLUDE_DIRECTORIES(${DRUMSTICK_FILE_INCLUDEDIR}) + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fexceptions") + BUILD_PLUGIN(midiexport MidiExport.cpp midiWriter.cpp + MOCFILES MidiExport.h midiWriter.h) + + IF(NOT LMMS_BUILD_LINUX) + IF(WANT_QT5) + TARGET_LINK_LIBRARIES(midiexport ${DRUMSTICK_FILE_LIBRARIES} Qt5::Core) + ELSE() + TARGET_LINK_LIBRARIES(midiexport ${DRUMSTICK_FILE_LIBRARIES} -lQtCore4) + ENDIF() + ELSE() + TARGET_LINK_LIBRARIES(midiexport ${DRUMSTICK_FILE_LIBRARIES}) + ENDIF() +ENDIF(LMMS_HAVE_FLUIDSYNTH) diff --git a/plugins/MidiExport/MidiExport.cpp b/plugins/MidiExport/MidiExport.cpp index 4cb0b356b88..7f0e6cd2966 100644 --- a/plugins/MidiExport/MidiExport.cpp +++ b/plugins/MidiExport/MidiExport.cpp @@ -28,8 +28,10 @@ #include #include #include +#include #include "MidiExport.h" +#include "midiWriter.h" #include "Engine.h" #include "TrackContainer.h" #include "InstrumentTrack.h" @@ -70,98 +72,12 @@ MidiExport::~MidiExport() bool MidiExport::tryExport( const TrackContainer::TrackList &tracks, int tempo, const QString &filename ) { - QFile f(filename); - f.open(QIODevice::WriteOnly); - QDataStream midiout(&f); - - InstrumentTrack* instTrack; - QDomElement element; - - - int nTracks = 0; - const int BUFFER_SIZE = 50*1024; - uint8_t buffer[BUFFER_SIZE]; - uint32_t size; - - for( const Track* track : tracks ) if( track->type() == Track::InstrumentTrack ) nTracks++; - - // midi header - MidiFile::MIDIHeader header(nTracks); - size = header.writeToBuffer(buffer); - midiout.writeRawData((char *)buffer, size); - - // midi tracks - for( Track* track : tracks ) - { - DataFile dataFile( DataFile::SongProject ); - MidiFile::MIDITrack mtrack; - - if( track->type() != Track::InstrumentTrack ) continue; - - //qDebug() << "exporting " << track->name(); - - - mtrack.addName(track->name().toStdString(), 0); - //mtrack.addProgramChange(0, 0); - mtrack.addTempo(tempo, 0); - - instTrack = dynamic_cast( track ); - element = instTrack->saveState( dataFile, dataFile.content() ); - - // instrumentTrack - // - instrumentTrack - // - pattern - int base_pitch = 0; - double base_volume = 1.0; - int base_time = 0; - - - for(QDomNode n = element.firstChild(); !n.isNull(); n = n.nextSibling()) - { - //QDomText txt = n.toText(); - //qDebug() << ">> child node " << n.nodeName(); - - if (n.nodeName() == "instrumenttrack") - { - // TODO interpret pan="0" fxch="0" usemasterpitch="1" pitchrange="1" pitch="0" basenote="57" - QDomElement it = n.toElement(); - base_pitch = it.attribute("pitch", "0").toInt(); - base_volume = it.attribute("volume", "100").toDouble()/100.0; - } - - if (n.nodeName() == "pattern") - { - base_time = n.toElement().attribute("pos", "0").toInt(); - // TODO interpret steps="12" muted="0" type="1" name="Piano1" len="2592" - for(QDomNode nn = n.firstChild(); !nn.isNull(); nn = nn.nextSibling()) - { - QDomElement note = nn.toElement(); - if (note.attribute("len", "0") == "0" || note.attribute("vol", "0") == "0") continue; - #if 0 - qDebug() << ">>>> key " << note.attribute( "key", "0" ) - << " " << note.attribute("len", "0") << " @" - << note.attribute("pos", "0"); - #endif - mtrack.addNote( - note.attribute("key", "0").toInt()+base_pitch - , 100 * base_volume * (note.attribute("vol", "100").toDouble()/100) - , (base_time+note.attribute("pos", "0").toDouble())/48 - , (note.attribute("len", "0")).toDouble()/48); - } - } - - } - size = mtrack.writeToBuffer(buffer); - midiout.writeRawData((char *)buffer, size); - } // for each track - + midiWriter s_mr( tracks ); + s_mr.writeFile( filename ); return true; - } - - void MidiExport::error() { //qDebug() << "MidiExport error: " << m_error ; diff --git a/plugins/MidiExport/MidiExport.h b/plugins/MidiExport/MidiExport.h index d829a8b8ff3..cb7b1aa4fb2 100644 --- a/plugins/MidiExport/MidiExport.h +++ b/plugins/MidiExport/MidiExport.h @@ -28,13 +28,11 @@ #include #include "ExportFilter.h" -#include "MidiFile.hpp" - class MidiExport: public ExportFilter { -// Q_OBJECT + Q_OBJECT public: MidiExport( ); ~MidiExport(); @@ -47,12 +45,8 @@ class MidiExport: public ExportFilter virtual bool tryExport( const TrackContainer::TrackList &tracks, int tempo, const QString &filename ); private: - - void error( void ); - - -} ; +}; #endif diff --git a/plugins/MidiExport/MidiFile.hpp b/plugins/MidiExport/MidiFile.hpp deleted file mode 100644 index 0e2bfbe5bd7..00000000000 --- a/plugins/MidiExport/MidiFile.hpp +++ /dev/null @@ -1,308 +0,0 @@ -#ifndef MIDIFILE_HPP -#define MIDIFILE_HPP - -/** - * Name: MidiFile.hpp - * Purpose: C++ re-write of the python module MidiFile.py - * Author: Mohamed Abdel Maksoud - *----------------------------------------------------------------------------- - * Name: MidiFile.py - * Purpose: MIDI file manipulation utilities - * - * Author: Mark Conway Wirt - * - * Created: 2008/04/17 - * Copyright: (c) 2009 Mark Conway Wirt - * License: Please see License.txt for the terms under which this - * software is distributed. - *----------------------------------------------------------------------------- - */ - -#include -#include -#include -#include -#include -#include -#include - -using std::string; -using std::vector; -using std::set; - -namespace MidiFile -{ - -const int TICKSPERBEAT = 128; - - -int writeVarLength(uint32_t val, uint8_t *buffer) -{ - /* - Accept an input, and write a MIDI-compatible variable length stream - - The MIDI format is a little strange, and makes use of so-called variable - length quantities. These quantities are a stream of bytes. If the most - significant bit is 1, then more bytes follow. If it is zero, then the - byte in question is the last in the stream - */ - int size = 0; - uint8_t result, little_endian[4]; - result = val & 0x7F; - little_endian[size++] = result; - val = val >> 7; - while (val > 0) - { - result = val & 0x7F; - result = result | 0x80; - little_endian[size++] = result; - val = val >> 7; - } - for (int i=0; i> 24; - buf[1] = val >> 16 & 0xff; - buf[2] = val >> 8 & 0xff; - buf[3] = val & 0xff; - return 4; -} - -int writeBigEndian2(uint16_t val, uint8_t *buf) -{ - buf[0] = val >> 8 & 0xff; - buf[1] = val & 0xff; - return 2; -} - - -class MIDIHeader -{ - // Class to encapsulate the MIDI header structure. - uint16_t numTracks; - uint16_t ticksPerBeat; - - public: - - MIDIHeader(uint16_t nTracks, uint16_t ticksPB=TICKSPERBEAT): numTracks(nTracks), ticksPerBeat(ticksPB) {} - - inline int writeToBuffer(uint8_t *buffer, int start=0) const - { - // chunk ID - buffer[start++] = 'M'; buffer[start++] = 'T'; buffer[start++] = 'h'; buffer[start++] = 'd'; - // chunk size (6 bytes always) - buffer[start++] = 0; buffer[start++] = 0; buffer[start++] = 0; buffer[start++] = 0x06; - // format: 1 (multitrack) - buffer[start++] = 0; buffer[start++] = 0x01; - - start += writeBigEndian2(numTracks, buffer+start); - - start += writeBigEndian2(ticksPerBeat, buffer+start); - - return start; - } - -}; - - -struct Event -{ - uint32_t time; - uint32_t tempo; - string trackName; - enum {NOTE_ON, NOTE_OFF, TEMPO, PROG_CHANGE, TRACK_NAME} type; - // TODO make a union to save up space - uint8_t pitch; - uint8_t programNumber; - uint8_t duration; - uint8_t volume; - uint8_t channel; - - Event() {time=tempo=pitch=programNumber=duration=volume=channel=0; trackName="";} - - inline int writeToBuffer(uint8_t *buffer) const - { - uint8_t code, fourbytes[4]; - int size=0; - switch (type) - { - case NOTE_ON: - code = 0x9 << 4 | channel; - size += writeVarLength(time, buffer+size); - buffer[size++] = code; - buffer[size++] = pitch; - buffer[size++] = volume; - break; - case NOTE_OFF: - code = 0x8 << 4 | channel; - size += writeVarLength(time, buffer+size); - buffer[size++] = code; - buffer[size++] = pitch; - buffer[size++] = volume; - break; - case TEMPO: - code = 0xFF; - size += writeVarLength(time, buffer+size); - buffer[size++] = code; - buffer[size++] = 0x51; - buffer[size++] = 0x03; - writeBigEndian4(int(60000000.0 / tempo), fourbytes); - - //printf("tempo of %x translates to ", tempo); - for (int i=0; i<3; i++) printf("%02x ", fourbytes[i+1]); - printf("\n"); - buffer[size++] = fourbytes[1]; - buffer[size++] = fourbytes[2]; - buffer[size++] = fourbytes[3]; - break; - case PROG_CHANGE: - code = 0xC << 4 | channel; - size += writeVarLength(time, buffer+size); - buffer[size++] = code; - buffer[size++] = programNumber; - break; - case TRACK_NAME: - size += writeVarLength(time, buffer+size); - buffer[size++] = 0xFF; - buffer[size++] = 0x03; - size += writeVarLength(trackName.size(), buffer+size); - trackName.copy((char *)(&buffer[size]), trackName.size()); - size += trackName.size(); -// buffer[size++] = '\0'; -// buffer[size++] = '\0'; - - break; - } - return size; - } // writeEventsToBuffer - - - // events are sorted by their time - inline bool operator < (const Event& b) const { - return this->time < b.time; - } -}; - -template -class MIDITrack -{ - // A class that encapsulates a MIDI track - // Nested class definitions. - vector events; - - public: - uint8_t channel; - - MIDITrack(): channel(0) {} - - inline void addEvent(const Event &e) - { - Event E = e; - events.push_back(E); - } - - inline void addNote(uint8_t pitch, uint8_t volume, double time, double duration) - { - Event event; event.channel = channel; - event.volume = volume; - - event.type = Event::NOTE_ON; event.pitch = pitch; event.time= (uint32_t) (time * TICKSPERBEAT); - addEvent(event); - - event.type = Event::NOTE_OFF; event.pitch = pitch; event.time=(uint32_t) ((time+duration) * TICKSPERBEAT); - addEvent(event); - - //printf("note: %d-%d\n", (uint32_t) time * TICKSPERBEAT, (uint32_t)((time+duration) * TICKSPERBEAT)); - } - - inline void addName(const string &name, uint32_t time) - { - Event event; event.channel = channel; - event.type = Event::TRACK_NAME; event.time=time; event.trackName = name; - addEvent(event); - } - - inline void addProgramChange(uint8_t prog, uint32_t time) - { - Event event; event.channel = channel; - event.type = Event::PROG_CHANGE; event.time=time; event.programNumber = prog; - addEvent(event); - } - - inline void addTempo(uint8_t tempo, uint32_t time) - { - Event event; event.channel = channel; - event.type = Event::TEMPO; event.time=time; event.tempo = tempo; - addEvent(event); - } - - inline int writeMIDIToBuffer(uint8_t *buffer, int start=0) const - { - // Write the meta data and note data to the packed MIDI stream. - // Process the events in the eventList - - start += writeEventsToBuffer(buffer, start); - - // Write MIDI close event. - buffer[start++] = 0x00; - buffer[start++] = 0xFF; - buffer[start++] = 0x2F; - buffer[start++] = 0x00; - - // return the entire length of the data and write to the header - - return start; - } - - inline int writeEventsToBuffer(uint8_t *buffer, int start=0) const - { - // Write the events in MIDIEvents to the MIDI stream. - vector _events = events; - std::sort(_events.begin(), _events.end()); - vector::const_iterator it; - uint32_t time_last = 0, tmp; - for (it = _events.begin(); it!=_events.end(); ++it) - { - Event e = *it; - if (e.time < time_last){ - printf("error: e.time=%d time_last=%d\n", e.time, time_last); - assert(false); - } - tmp = e.time; - e.time -= time_last; - time_last = tmp; - start += e.writeToBuffer(buffer+start); - if (start >= MAX_TRACK_SIZE) { - break; - } - } - return start; - } - - inline int writeToBuffer(uint8_t *buffer, int start=0) const - { - uint8_t eventsBuffer[MAX_TRACK_SIZE]; - uint32_t events_size = writeMIDIToBuffer(eventsBuffer); - //printf(">> track %lu events took 0x%x bytes\n", events.size(), events_size); - - // chunk ID - buffer[start++] = 'M'; buffer[start++] = 'T'; buffer[start++] = 'r'; buffer[start++] = 'k'; - // chunk size - start += writeBigEndian4(events_size, buffer+start); - // copy events data - memmove(buffer+start, eventsBuffer, events_size); - start += events_size; - return start; - } -}; - -}; // namespace - -#endif diff --git a/plugins/MidiExport/midiWriter.cpp b/plugins/MidiExport/midiWriter.cpp new file mode 100644 index 00000000000..b8f9f6f0dbd --- /dev/null +++ b/plugins/MidiExport/midiWriter.cpp @@ -0,0 +1,538 @@ +/* + * midiWriter.cpp - export backend. + * + * Copyright (c) 2016-2017 Tony Chyi + * + * This file is part of LMMS - http://lmms.io + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program (see COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + */ + +#include +#include +#include +#include + +#include "AutomationTrack.h" +#include "Engine.h" +#include "Instrument.h" +#include "InstrumentTrack.h" +#include "Midi.h" +#include "MidiEvent.h" +#include "MidiTime.h" +#include "Note.h" +#include "Pattern.h" +#include "Song.h" +#include "Track.h" + +#include "midiWriter.h" + +#define SEARCH_AUTOTRACKS_START( automodel ) \ + for( int autoTrackIndex : AutomationTracks ) \ + { \ + AutomationTrack * autoTrack = dynamic_cast< AutomationTrack * >( m_tl[ autoTrackIndex ] ); \ + for( TrackContentObject * tco : autoTrack->getTCOs() ) \ + { \ + tick_t offset = tco->startPosition().getTicks();\ + AutomationPattern * a_tco = dynamic_cast< AutomationPattern * >( tco ); \ + if( a_tco->firstObject() != automodel ) \ + continue; \ + const AutomationPattern::timeMap tp = a_tco->getTimeMap(); \ + AutomationPattern::timeMap::const_iterator i; \ + for( i=tp.constBegin(); i!=tp.constEnd(); ++i ) \ + {\ + +#define SEARCH_AUTOTRACKS_END \ + }\ + }\ + } + +#define PROGRESSION_CALC_START \ + if( i != tp.constBegin() && \ + a_tco->progressionType() != AutomationPattern::DiscreteProgression &&\ + i.value() != (i-1).value() )\ + {\ + for( int ii = 1; ii < i.key() - (i-1).key(); ii++ ) \ + {\ + +#define PROGRESSION_CALC_END \ + }\ + }\ + + +#define INSERT_CC_EVENT( type, zoom, t, offset, val ) \ + event = new MidiEvent( MidiControlChange );\ + event->setChannel( currentChannel );\ + event->setControllerNumber( type );\ + event->setControllerValue( val * zoom );\ + EventList.insert( t+offset, event ); + +#define INSERT_PITCH_EVENT( t, offset, val ) \ + event = new MidiEvent( MidiPitchBend );\ + event->setChannel( currentChannel );\ + event->setPitchBend( val / pitchBendMultiply * ( MidiMaxPitchBend / 2 ) / 100 + ( MidiMaxPitchBend / 2 + 1 ) );\ + EventList.insert( t+offset, event ); + +#define INSERT_PROG_EVENT( t, offset, val ) \ + event = new MidiEvent( MidiProgramChange ); \ + event->setChannel( currentChannel );\ + event->setParam( 0, val ); \ + EventList.insert( t+offset, event ); + +#define INSERT_TEMPO_EVENT( t, val ) \ + MidiEvent * event = new MidiEvent( MidiMetaEvent ); \ + event->setMetaEvent( MidiSetTempo ); \ + event->setParam( 0, val ); \ + EventList.insert( t, event ); \ + +#define FIND_PITCH_MULTYPLY( t ) \ + QMap< int, int >::const_iterator j;\ + for( j=PitchBendMultiply.constBegin(); j!=PitchBendMultiply.constEnd(); ++j )\ + {\ + if( j.key() <= t )\ + pitchBendMultiply = j.value();\ + else\ + break;\ + } + + + +midiWriter::midiWriter( const TrackContainer::TrackList &tracks ): + m_tl( tracks ), m_seq( new drumstick::QSmf( this ) ), + flagRpnPitchBendRangeSent( false ) +{ + tempoPat = Engine::getSong()->tempoAutomationPattern(); + + MeterModel & timeSigMM = Engine::getSong()->getTimeSigModel(); + timeSigNumPat = AutomationPattern::globalAutomationPattern( + &timeSigMM.numeratorModel() ); + timeSigDenPat = AutomationPattern::globalAutomationPattern( + &timeSigMM.denominatorModel() ); + + for( int i=0; i < MidiChannelCount; i++) + ChannelProg[i] = -1; + + // We need multi-track format, so set format 1. + m_seq->setFileFormat( 1 ); + + int trackIndex = 0; + for( const Track* track : tracks ) + { + switch( track->type() ) + { + case Track::InstrumentTrack: + if( dynamic_cast< const InstrumentTrack * >( track )->instrumentName() == "Sf2 Player" ) + InstrumentTracks << trackIndex; + break; + case Track::AutomationTrack: + AutomationTracks << trackIndex; + break; + default: + break; + } + trackIndex++; + } + + // Track 0 is to write global events. + m_seq->setTracks( InstrumentTracks.size()+1 ); + // 1 quarter note = 960 midi ticks. + m_seq->setDivision( DefaultMidiDivision ); + + tickRate = (double)DefaultTicksPerTact / 4.f / DefaultMidiDivision ; + + connect( m_seq, SIGNAL( signalSMFWriteTrack(int) ), + this, SLOT( writeTrackEvent(int)) ); + +} + + +midiWriter::~midiWriter() +{ + printf( "destroy midiWriter\n" ); + delete m_seq; +} + +void midiWriter::writeFile( const QString &fileName ) +{ + + m_seq->writeToFile( fileName ); +} + +void midiWriter::writeEventToFile() +{ + int lastTime = 0; + QMultiMap< long, MidiEvent * >::const_iterator i; + + for( i=EventList.constBegin(); i!=EventList.constEnd(); ++i ) + { + MidiEvent *event = i.value(); + int deltaTime = i.key() - lastTime; + double realDeltaTime = deltaTime / tickRate; + + switch( event->type() ) + { + case MidiMetaEvent: + switch( event->metaEvent() ) + { + case MidiTimeSignature: + m_seq->writeTimeSignature( (long) realDeltaTime, event->param( 0 ), + intSqrt( event->param( 1 ) ), + DefaultMidiClockPerMetronomeClick * 4 / + event->param(1), 8 ); + + break; + + case MidiSetTempo: + m_seq->writeBpmTempo( (int) realDeltaTime, event->param( 0 ) ); + break; + + default: + break; + } + break; + + case MidiControlChange: + switch( event->controllerNumber() ) + { + case MidiControllerPan: + m_seq->writeMidiEvent( (long) realDeltaTime, control_change, + event->channel(), + MidiControllerPan, + event->controllerValue() - 127 ); + + break; + case MidiControllerDataEntry: + if( !flagRpnPitchBendRangeSent && event->controllerValue() > 2 ) + { + m_seq->writeMidiEvent( (long) realDeltaTime, control_change, + event->channel(), + MidiControllerRegisteredParameterNumberMSB, + 0 ); + m_seq->writeMidiEvent( 0, control_change, + event->channel(), + MidiControllerRegisteredParameterNumberLSB, + MidiPitchBendSensitivityRPN ); + m_seq->writeMidiEvent( 0, control_change, + event->channel(), + MidiControllerDataEntry, + event->controllerValue() ); + m_seq->writeMidiEvent( 0, control_change, + event->channel(), + 38, 0); + + flagRpnPitchBendRangeSent = true; + } + else if( event->controllerValue() <= 2 ) + break; + default: + m_seq->writeMidiEvent( (long) realDeltaTime, control_change, + event->channel(), + event->controllerNumber(), + event->controllerValue() ); + + } + break; + + case MidiPitchBend: + m_seq->writeMidiEvent( (long) realDeltaTime, pitch_wheel, + event->channel(), + event->pitchBend() & 127, + (event->pitchBend() & 32512) >> 7 ); + break; + + case MidiProgramChange: + m_seq->writeMidiEvent( (long) realDeltaTime, program_chng, + event->channel(), event->program() ); + break; + + case MidiNoteOff: + case MidiNoteOn: + m_seq->writeMidiEvent( (long) realDeltaTime, event->type(), + event->channel(), event->param(0), + event->param(1) ); + break; + + default: + break; + } + lastTime = i.key(); + + // Should release memory use. + delete event; + } +} + +void midiWriter::insertTempoEvent() +{ + AutomationPattern::timeMap time_map = tempoPat->getTimeMap(); + AutomationPattern::timeMap::const_iterator i; + AutomationPattern::timeMap::const_iterator last = time_map.constBegin() ; + + for( i = time_map.constBegin(); i != time_map.constEnd(); ++i ) + { + if( i != time_map.constBegin() && + tempoPat->progressionType() != AutomationPattern::DiscreteProgression && + i.value() != last.value() ) + { + for(int ii = 1; ii < i.key() - (i-1).key(); ii++ ) + { + INSERT_TEMPO_EVENT( (i-1).key()+ii, tempoPat->valueAt( MidiTime( (i-1).key()+ii ) ) ); + } + } + INSERT_TEMPO_EVENT( i.key(), i.value() ); + last = i; + } +} + +void midiWriter::insertTimeSigEvent() +{ + // In LMMS, time sig will not always appear at the same time. + AutomationPattern::timeMap & timeSigNum_map = timeSigNumPat->getTimeMap(); + AutomationPattern::timeMap & timeSigDen_map = timeSigDenPat->getTimeMap(); + AutomationPattern::timeMap::const_iterator i; + + QList TimeList; + + // Step 1, fetch time point from Num and Den. + for( i=timeSigNum_map.constBegin(); i!=timeSigNum_map.constEnd(); ++i ) + TimeList << i.key(); + for( i=timeSigDen_map.constBegin(); i!=timeSigDen_map.constEnd(); ++i ) + TimeList << i.key(); + + // Step 2, unique and sort. + TimeList = TimeList.toSet().toList(); + sort(TimeList.begin(), TimeList.end()); + + // Step 3, insert event. + uint16_t num=4, den=2; + + for( int i : TimeList ) + { + if( timeSigDen_map.contains(i) ) + num = (uint16_t) timeSigDen_map[i]; + if( timeSigNum_map.contains(i) ) + den = (uint16_t) timeSigNum_map[i]; + + MidiEvent * event = new MidiEvent( MidiMetaEvent ); + event->setMetaEvent( MidiTimeSignature ); + /* + * Param 0 => Numerator + * Param 1 => Denominator + */ + event->setParam( 0, num ); + event->setParam( 1, den ); + EventList.insert( i, event ); + } +} + +void midiWriter::insertCCEvent( InstrumentTrack *track ) +{ + MidiEvent * event; + + INSERT_CC_EVENT( MidiControllerPan, 127 / 100 + 127, 0, 0, track->panningModel()->value() ); + SEARCH_AUTOTRACKS_START( track->panningModel() ); + PROGRESSION_CALC_START; + INSERT_CC_EVENT( MidiControllerPan, 127 / 100 + 127, (i-1).key(), offset, a_tco->valueAt( MidiTime( (i-1).key()+ii+offset ) ) ); + PROGRESSION_CALC_END; + INSERT_CC_EVENT( MidiControllerPan, 127 / 100 + 127, i.key(), offset, i.value() ); + SEARCH_AUTOTRACKS_END; + + INSERT_CC_EVENT( MidiControllerMainVolume, 127 / 200, 0, 0, track->volumeModel()->value() ); + SEARCH_AUTOTRACKS_START( track->volumeModel() ); + PROGRESSION_CALC_START; + INSERT_CC_EVENT( MidiControllerMainVolume, 127 / 200, (i-1).key(), offset, a_tco->valueAt( MidiTime( (i-1).key()+ii+offset ) ) ); + PROGRESSION_CALC_END; + INSERT_CC_EVENT( MidiControllerMainVolume, 127 / 200, i.key(), offset, i.value() ); + SEARCH_AUTOTRACKS_END; + + PitchBendMultiply.insert( 0, track->pitchRangeModel()->value() ); + INSERT_CC_EVENT( MidiControllerDataEntry, 1, 0, 0, track->pitchRangeModel()->value() ); + SEARCH_AUTOTRACKS_START( track->pitchRangeModel() ); + PROGRESSION_CALC_START; + PitchBendMultiply.insert( (i-1).key()+ii+offset, a_tco->valueAt( MidiTime( (i-1).key()+ii ) ) ); + INSERT_CC_EVENT( MidiControllerDataEntry, 1, (i-1).key(), offset, a_tco->valueAt( MidiTime( (i-1).key()+ii+offset ) ) ); + PROGRESSION_CALC_END; + PitchBendMultiply.insert( i.key()+offset, i.value() ); + INSERT_CC_EVENT( MidiControllerDataEntry, 1, i.key(), offset, i.value() ); + SEARCH_AUTOTRACKS_END; + + // DrumChannel(10) no need bank select. + if( currentChannel == DrumChannel ) + return; + INSERT_CC_EVENT( MidiControllerBankSelect, 1, 0, 0, dynamic_cast< IntModel * >( track->instrument()->childModel( "bank" ) )->value() ); + SEARCH_AUTOTRACKS_START( track->instrument()->childModel( "bank" ) ); + PROGRESSION_CALC_START; + INSERT_CC_EVENT( MidiControllerBankSelect, 1, (i-1).key(), offset, a_tco->valueAt( MidiTime( (i-1).key()+ii+offset ) ) ); + PROGRESSION_CALC_END; + INSERT_CC_EVENT( MidiControllerBankSelect, 1, i.key(), offset, i.value() ); + SEARCH_AUTOTRACKS_END; +} + +void midiWriter::insertNoteEvent( InstrumentTrack *track ) +{ + MidiEvent * event; + for( TrackContentObject * tco : track->getTCOs() ) + { + tick_t offset = tco->startPosition().getTicks(); + const NoteVector ¬es = dynamic_cast< Pattern * >( tco )->notes(); + + for( Note * i : notes ) + { + flagCurrentTrackHasNotes = true; + + event = new MidiEvent( MidiNoteOn ); + // TODO: How can i set the channel? + event->setChannel( currentChannel ); + event->setParam( 0, i->key()+12 ); + event->setParam( 1, i->getVolume()*127/200 ); + EventList.insert( i->pos().getTicks()+offset, event ); + + event = new MidiEvent( MidiNoteOff ); + event->setChannel( currentChannel ); + event->setParam( 0, i->key()+12 ); + event->setParam( 1, 64 ); + EventList.insert( i->endPos().getTicks()+offset, event ); + } + } +} + +void midiWriter::insertProgramEvent( InstrumentTrack *track ) +{ + MidiEvent * event; + INSERT_PROG_EVENT( 0, 0, dynamic_cast( track->instrument()->childModel( "patch" ) )->value() ); + + SEARCH_AUTOTRACKS_START( track->instrument()->childModel( "patch" ) ); + INSERT_PROG_EVENT( i.key(), offset, i.value() ); + SEARCH_AUTOTRACKS_END; +} + +void midiWriter::insertPitchEvent( InstrumentTrack *track ) +{ + MidiEvent * event; + int pitchBendMultiply; + pitchBendMultiply = track->pitchRangeModel()->value(); + INSERT_PITCH_EVENT( 0, 0, track->pitchModel()->value() ); + + SEARCH_AUTOTRACKS_START( track->pitchModel() ) + PROGRESSION_CALC_START; + FIND_PITCH_MULTYPLY( (i-1).key() + ii + offset ); + INSERT_PITCH_EVENT( (i-1).key() + ii, offset, a_tco->valueAt( MidiTime( (i-1).key()+ii+offset ) ) ); + PROGRESSION_CALC_END; + + FIND_PITCH_MULTYPLY( i.key() + offset ); + INSERT_PITCH_EVENT( i.key(), offset, i.value() ); + SEARCH_AUTOTRACKS_END +} + +void midiWriter::allocateChannel( InstrumentTrack *track ) +{ + // patman not supported. + if( track->instrumentName() == "Sf2 Player" ) + { + Instrument * tr_inst = track->instrument(); + + // Find drumkit. + if( dynamic_cast< IntModel *>( tr_inst->childModel( "bank" ) )->value() >= 128 ) + { + currentChannel = DrumChannel; + return; + } + + int8_t prog = dynamic_cast< IntModel * >( tr_inst->childModel( "patch" ) )->value(); + + // Step 1: Find unused channel + for( currentChannel = 0; currentChannel < MidiChannelCount; currentChannel++ ) + { + if ( currentChannel == DrumChannel ) + continue; + + if ( ChannelProg[ currentChannel ] == -1 ) + { + ChannelProg[ currentChannel ] = prog; + return; + } + } + + // Step 2: If no unused channel found, find channel has the same program. + for( currentChannel = 0; currentChannel < MidiChannelCount; currentChannel++ ) + { + if ( currentChannel == DrumChannel ) + continue; + + if( ChannelProg[ currentChannel ] == prog ) + return; + } + + // Step 3: If no suitable channel, give it randomly. + do + { + currentChannel = qrand() % 16; + } while( currentChannel == DrumChannel ); + } +} + +// Just for 2, 4, 8, 16 etc. +int midiWriter::intSqrt( int n ) +{ + int i; + for( i = 0; n >> i < 1; i++ ); + return i; +} + +// Slot +void midiWriter::writeTrackEvent( int track ) +{ + DataFile dataFile( DataFile::SongProject ); + flagRpnPitchBendRangeSent = false; + flagCurrentTrackHasNotes = false; + currentChannel = -1; + + EventList.clear(); + PitchBendMultiply.clear(); + + if( track == 0 ) + { + // Export by LMMS + m_seq->writeMetaEvent( 0, copyright_notice, EXPORTED_BY ); + m_seq->writeMidiEvent( 0, system_exclusive, 5, GM_SYSEX ); + insertTempoEvent(); + insertTimeSigEvent(); + } + else + { + // Write track name. + Track* currentTrack = m_tl[ InstrumentTracks[ track-1 ] ]; + m_seq->writeMetaEvent( 0, sequence_name, currentTrack->name() ); + + // TODO: Get tracks and automation pattern. + InstrumentTrack *instTrack = dynamic_cast< InstrumentTrack* >( currentTrack ); + instTrack->saveState( dataFile, dataFile.content() ); + allocateChannel( instTrack ); + + // When read EventList, it will be reverse. + insertNoteEvent( instTrack ); + insertCCEvent( instTrack ); + insertPitchEvent( instTrack ); + insertProgramEvent( instTrack ); + } + + // Write event to file; + if( flagCurrentTrackHasNotes || track == 0 ) + writeEventToFile(); + // Do not forget ending this track. + m_seq->writeMetaEvent( 0, end_of_track ); +} diff --git a/plugins/MidiExport/midiWriter.h b/plugins/MidiExport/midiWriter.h new file mode 100644 index 00000000000..79d8bf34d02 --- /dev/null +++ b/plugins/MidiExport/midiWriter.h @@ -0,0 +1,99 @@ +/* + * midiWriter.h - export backend. + * + * Copyright (c) 2016-2017 Tony Chyi + * + * This file is part of LMMS - http://lmms.io + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program (see COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + */ + +#ifndef MIDI_WRITER_H +#define MIDI_WRITER_H + +#include +#include + +#include "AutomationPattern.h" +#include "Engine.h" +#include "MeterModel.h" +#include "Midi.h" +#include "MidiEvent.h" +#include "TrackContainer.h" + +#define EXPORTED_BY QString( "Exported by LMMS" ) +#define GM_SYSEX QByteArray( "\x7e\x7f\x09\x01" ) + (char) end_of_sysex + +const int DefaultPitchMultiply = 2; +// If TimSig = 4/4, set midi clock = 24; +const int DefaultMidiClockPerMetronomeClick = 24; +const int DefaultMidiDivision = 960; +// Channel 10 in SMF is drum kit. +const int DrumChannel = 9; + +class midiWriter: public QObject +{ + Q_OBJECT +public: + midiWriter( const TrackContainer::TrackList &tracks ); + ~midiWriter(); + void writeFile( const QString &fileName ); + +public slots: + void writeTrackEvent( int track ); + +private: + /* + * Write event to file here. But need to delete event after write, + * otherwise it will cause memory leak. + */ + void writeEventToFile(); + + // All methods below will create new MidiEvent, MUST delete it later! + void insertTempoEvent(); + void insertTimeSigEvent(); + void insertNoteEvent( InstrumentTrack *track ); + + void insertCCEvent( InstrumentTrack *track ); + void insertProgramEvent( InstrumentTrack *track ); + void insertPitchEvent( InstrumentTrack *track ); + + void allocateChannel(InstrumentTrack *track ); + int intSqrt( int n ); + + const TrackContainer::TrackList &m_tl; + drumstick::QSmf *m_seq; + + QMultiMap< long, MidiEvent* > EventList; + QMap< int, int > PitchBendMultiply; + QList< int > InstrumentTracks; + QList< int > AutomationTracks; + int8_t ChannelProg[ MidiChannelCount ]; + + AutomationPattern* tempoPat; + AutomationPattern* timeSigNumPat; + AutomationPattern* timeSigDenPat; + + double tickRate; + int8_t currentChannel; + + // LMMS just only export pitch bend range. + bool flagRpnPitchBendRangeSent; + bool flagCurrentTrackHasNotes; +}; + +#endif diff --git a/plugins/MidiImport/CMakeLists.txt b/plugins/MidiImport/CMakeLists.txt deleted file mode 100644 index 9fd6b6876e5..00000000000 --- a/plugins/MidiImport/CMakeLists.txt +++ /dev/null @@ -1,8 +0,0 @@ -INCLUDE(BuildPlugin) - -BUILD_PLUGIN(midiimport MidiImport.cpp MidiImport.h - portsmf/allegro.cpp portsmf/allegro.h portsmf/allegrosmfwr.cpp - portsmf/allegrord.cpp portsmf/allegrowr.cpp portsmf/allegrosmfrd.cpp - portsmf/mfmidi.cpp portsmf/mfmidi.h portsmf/strparse.cpp - portsmf/strparse.h portsmf/algrd_internal.h portsmf/algsmfrd_internal.h - portsmf/trace.h MOCFILES MidiImport.h) diff --git a/plugins/MidiImport/MidiImport.cpp b/plugins/MidiImport/MidiImport.cpp deleted file mode 100644 index e5a9b02a31c..00000000000 --- a/plugins/MidiImport/MidiImport.cpp +++ /dev/null @@ -1,618 +0,0 @@ -/* - * MidiImport.cpp - support for importing MIDI files - * - * Copyright (c) 2005-2014 Tobias Doerffel - * - * This file is part of LMMS - http://lmms.io - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public - * License along with this program (see COPYING); if not, write to the - * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301 USA. - * - */ - - -#include -#include -#include -#include -#include - -#include "MidiImport.h" -#include "TrackContainer.h" -#include "InstrumentTrack.h" -#include "AutomationTrack.h" -#include "AutomationPattern.h" -#include "ConfigManager.h" -#include "Pattern.h" -#include "Instrument.h" -#include "GuiApplication.h" -#include "MainWindow.h" -#include "MidiTime.h" -#include "debug.h" -#include "embed.h" -#include "Song.h" - -#include "portsmf/allegro.h" - -#define makeID(_c0, _c1, _c2, _c3) \ - ( 0 | \ - ( ( _c0 ) | ( ( _c1 ) << 8 ) | ( ( _c2 ) << 16 ) | ( ( _c3 ) << 24 ) ) ) - - - -extern "C" -{ - -Plugin::Descriptor PLUGIN_EXPORT midiimport_plugin_descriptor = -{ - STRINGIFY( PLUGIN_NAME ), - "MIDI Import", - QT_TRANSLATE_NOOP( "pluginBrowser", - "Filter for importing MIDI-files into LMMS" ), - "Tobias Doerffel ", - 0x0100, - Plugin::ImportFilter, - NULL, - NULL, - NULL -} ; - -} - - -MidiImport::MidiImport( const QString & _file ) : - ImportFilter( _file, &midiimport_plugin_descriptor ), - m_events(), - m_timingDivision( 0 ) -{ -} - - - - -MidiImport::~MidiImport() -{ -} - - - - -bool MidiImport::tryImport( TrackContainer* tc ) -{ - if( openFile() == false ) - { - return false; - } - -#ifdef LMMS_HAVE_FLUIDSYNTH - if( gui != NULL && - ConfigManager::inst()->defaultSoundfont().isEmpty() ) - { - QMessageBox::information( gui->mainWindow(), - tr( "Setup incomplete" ), - tr( "You do not have set up a default soundfont in " - "the settings dialog (Edit->Settings). " - "Therefore no sound will be played back after " - "importing this MIDI file. You should download " - "a General MIDI soundfont, specify it in " - "settings dialog and try again." ) ); - } -#else - if( gui ) - { - QMessageBox::information( gui->mainWindow(), - tr( "Setup incomplete" ), - tr( "You did not compile LMMS with support for " - "SoundFont2 player, which is used to add default " - "sound to imported MIDI files. " - "Therefore no sound will be played back after " - "importing this MIDI file." ) ); - } -#endif - - switch( readID() ) - { - case makeID( 'M', 'T', 'h', 'd' ): - printf( "MidiImport::tryImport(): found MThd\n"); - return readSMF( tc ); - - case makeID( 'R', 'I', 'F', 'F' ): - printf( "MidiImport::tryImport(): found RIFF\n"); - return readRIFF( tc ); - - default: - printf( "MidiImport::tryImport(): not a Standard MIDI " - "file\n" ); - return false; - } -} - - - - -class smfMidiCC -{ - -public: - smfMidiCC() : - at( NULL ), - ap( NULL ), - lastPos( 0 ) - { } - - AutomationTrack * at; - AutomationPattern * ap; - MidiTime lastPos; - - smfMidiCC & create( TrackContainer* tc, QString tn ) - { - if( !at ) - { - // Keep LMMS responsive, for now the import runs - // in the main thread. This should probably be - // removed if that ever changes. - qApp->processEvents(); - at = dynamic_cast( Track::create( Track::AutomationTrack, tc ) ); - } - if( tn != "") { - at->setName( tn ); - } - return *this; - } - - - void clear() - { - at = NULL; - ap = NULL; - lastPos = 0; - } - - - smfMidiCC & putValue( MidiTime time, AutomatableModel * objModel, float value ) - { - if( !ap || time > lastPos + DefaultTicksPerTact ) - { - MidiTime pPos = MidiTime( time.getTact(), 0 ); - ap = dynamic_cast( - at->createTCO(0) ); - ap->movePosition( pPos ); - ap->addObject( objModel ); - } - - lastPos = time; - time = time - ap->startPosition(); - ap->putValue( time, value, false ); - ap->changeLength( MidiTime( time.getTact() + 1, 0 ) ); - - return *this; - } -}; - - - -class smfMidiChannel -{ - -public: - smfMidiChannel() : - it( NULL ), - p( NULL ), - it_inst( NULL ), - isSF2( false ), - hasNotes( false ), - lastEnd( 0 ) - { } - - InstrumentTrack * it; - Pattern* p; - Instrument * it_inst; - bool isSF2; - bool hasNotes; - MidiTime lastEnd; - QString trackName; - - smfMidiChannel * create( TrackContainer* tc, QString tn ) - { - if( !it ) { - // Keep LMMS responsive - qApp->processEvents(); - it = dynamic_cast( Track::create( Track::InstrumentTrack, tc ) ); - -#ifdef LMMS_HAVE_FLUIDSYNTH - it_inst = it->loadInstrument( "sf2player" ); - - if( it_inst ) - { - isSF2 = true; - it_inst->loadFile( ConfigManager::inst()->defaultSoundfont() ); - it_inst->childModel( "bank" )->setValue( 0 ); - it_inst->childModel( "patch" )->setValue( 0 ); - } - else - { - it_inst = it->loadInstrument( "patman" ); - } -#else - it_inst = it->loadInstrument( "patman" ); -#endif - trackName = tn; - if( trackName != "") { - it->setName( tn ); - } - lastEnd = 0; - // General MIDI default - it->pitchRangeModel()->setInitValue( 2 ); - } - return this; - } - - - void addNote( Note & n ) - { - if( !p || n.pos() > lastEnd + DefaultTicksPerTact ) - { - MidiTime pPos = MidiTime( n.pos().getTact(), 0 ); - p = dynamic_cast( it->createTCO( 0 ) ); - p->movePosition( pPos ); - } - hasNotes = true; - lastEnd = n.pos() + n.length(); - n.setPos( n.pos( p->startPosition() ) ); - p->addNote( n, false ); - } - -}; - - -bool MidiImport::readSMF( TrackContainer* tc ) -{ - QString filename = file().fileName(); - closeFile(); - - const int preTrackSteps = 2; - QProgressDialog pd( TrackContainer::tr( "Importing MIDI-file..." ), - TrackContainer::tr( "Cancel" ), 0, preTrackSteps, gui->mainWindow() ); - pd.setWindowTitle( TrackContainer::tr( "Please wait..." ) ); - pd.setWindowModality(Qt::WindowModal); - pd.setMinimumDuration( 0 ); - - pd.setValue( 0 ); - - Alg_seq_ptr seq = new Alg_seq(filename.toLocal8Bit(), true); - seq->convert_to_beats(); - - pd.setMaximum( seq->tracks() + preTrackSteps ); - pd.setValue( 1 ); - - // 128 CC + Pitch Bend - smfMidiCC ccs[129]; - smfMidiChannel chs[256]; - - MeterModel & timeSigMM = Engine::getSong()->getTimeSigModel(); - AutomationPattern * timeSigNumeratorPat = - AutomationPattern::globalAutomationPattern( &timeSigMM.numeratorModel() ); - AutomationPattern * timeSigDenominatorPat = - AutomationPattern::globalAutomationPattern( &timeSigMM.denominatorModel() ); - - // TODO: adjust these to Time.Sig changes - double beatsPerTact = 4; - double ticksPerBeat = DefaultTicksPerTact / beatsPerTact; - - // Time-sig changes - Alg_time_sigs * timeSigs = &seq->time_sig; - for( int s = 0; s < timeSigs->length(); ++s ) - { - Alg_time_sig timeSig = (*timeSigs)[s]; - // Initial timeSig, set song-default value - if(/* timeSig.beat == 0*/ true ) - { - // TODO set song-global default value - printf("Another timesig at %f\n", timeSig.beat); - timeSigNumeratorPat->putValue( timeSig.beat*ticksPerBeat, timeSig.num ); - timeSigDenominatorPat->putValue( timeSig.beat*ticksPerBeat, timeSig.den ); - } - else - { - } - - } - - pd.setValue( 2 ); - - // Tempo stuff - AutomationPattern * tap = tc->tempoAutomationPattern(); - if( tap ) - { - tap->clear(); - Alg_time_map * timeMap = seq->get_time_map(); - Alg_beats & beats = timeMap->beats; - for( int i = 0; i < beats.len - 1; i++ ) - { - Alg_beat_ptr b = &(beats[i]); - double tempo = ( beats[i + 1].beat - b->beat ) / - ( beats[i + 1].time - beats[i].time ); - tap->putValue( b->beat * ticksPerBeat, tempo * 60.0 ); - } - if( timeMap->last_tempo_flag ) - { - Alg_beat_ptr b = &( beats[beats.len - 1] ); - tap->putValue( b->beat * ticksPerBeat, timeMap->last_tempo * 60.0 ); - } - } - - // Song events - for( int e = 0; e < seq->length(); ++e ) - { - Alg_event_ptr evt = (*seq)[e]; - - if( evt->is_update() ) - { - printf("Unhandled SONG update: %d %f %s\n", - evt->get_type_code(), evt->time, evt->get_attribute() ); - } - } - - // Tracks - for( int t = 0; t < seq->tracks(); ++t ) - { - QString trackName = QString( tr( "Track" ) + " %1" ).arg( t ); - Alg_track_ptr trk = seq->track( t ); - pd.setValue( t + preTrackSteps ); - - for( int c = 0; c < 129; c++ ) - { - ccs[c].clear(); - } - - // Now look at events - for( int e = 0; e < trk->length(); ++e ) - { - Alg_event_ptr evt = (*trk)[e]; - - if( evt->chan == -1 ) - { - bool handled = false; - if( evt->is_update() ) - { - QString attr = evt->get_attribute(); - if( attr == "tracknames" && evt->get_update_type() == 's' ) { - trackName = evt->get_string_value(); - handled = true; - } - } - if( !handled ) { - // Write debug output - printf("MISSING GLOBAL HANDLER\n"); - printf(" Chn: %d, Type Code: %d, Time: %f", (int) evt->chan, - evt->get_type_code(), evt->time ); - if ( evt->is_update() ) - { - printf( ", Update Type: %s", evt->get_attribute() ); - if ( evt->get_update_type() == 'a' ) - { - printf( ", Atom: %s", evt->get_atom_value() ); - } - } - printf( "\n" ); - } - } - else if( evt->is_note() && evt->chan < 256 ) - { - smfMidiChannel * ch = chs[evt->chan].create( tc, trackName ); - Alg_note_ptr noteEvt = dynamic_cast( evt ); - int ticks = noteEvt->get_duration() * ticksPerBeat; - Note n( (ticks < 1 ? 1 : ticks ), - noteEvt->get_start_time() * ticksPerBeat, - noteEvt->get_identifier() - 12, - noteEvt->get_loud()); - ch->addNote( n ); - - } - - else if( evt->is_update() ) - { - smfMidiChannel * ch = chs[evt->chan].create( tc, trackName ); - - double time = evt->time*ticksPerBeat; - QString update( evt->get_attribute() ); - - if( update == "programi" ) - { - long prog = evt->get_integer_value(); - if( ch->isSF2 ) - { - ch->it_inst->childModel( "bank" )->setValue( 0 ); - ch->it_inst->childModel( "patch" )->setValue( prog ); - } - else { - const QString num = QString::number( prog ); - const QString filter = QString().fill( '0', 3 - num.length() ) + num + "*.pat"; - const QString dir = "/usr/share/midi/" - "freepats/Tone_000/"; - const QStringList files = QDir( dir ). - entryList( QStringList( filter ) ); - if( ch->it_inst && !files.empty() ) - { - ch->it_inst->loadFile( dir+files.front() ); - } - } - } - - else if( update.startsWith( "control" ) || update == "bendr" ) - { - int ccid = update.mid( 7, update.length()-8 ).toInt(); - if( update == "bendr" ) - { - ccid = 128; - } - if( ccid <= 128 ) - { - double cc = evt->get_real_value(); - AutomatableModel * objModel = NULL; - - switch( ccid ) - { - case 0: - if( ch->isSF2 && ch->it_inst ) - { - objModel = ch->it_inst->childModel( "bank" ); - printf("BANK SELECT %f %d\n", cc, (int)(cc*127.0)); - cc *= 127.0f; - } - break; - - case 7: - objModel = ch->it->volumeModel(); - cc *= 100.0f; - break; - - case 10: - objModel = ch->it->panningModel(); - cc = cc * 200.f - 100.0f; - break; - - case 128: - objModel = ch->it->pitchModel(); - cc = cc * 100.0f; - break; - default: - //TODO: something useful for other CCs - break; - } - - if( objModel ) - { - if( time == 0 && objModel ) - { - objModel->setInitValue( cc ); - } - else - { - if( ccs[ccid].at == NULL ) { - ccs[ccid].create( tc, trackName + " > " + ( - objModel != NULL ? - objModel->displayName() : - QString("CC %1").arg(ccid) ) ); - } - ccs[ccid].putValue( time, objModel, cc ); - } - } - } - } - else { - printf("Unhandled update: %d %d %f %s\n", (int) evt->chan, - evt->get_type_code(), evt->time, evt->get_attribute() ); - } - } - } - } - - delete seq; - - - for( int c=0; c < 256; ++c ) - { - if( !chs[c].hasNotes && chs[c].it ) - { - printf(" Should remove empty track\n"); - // must delete trackView first - but where is it? - //tc->removeTrack( chs[c].it ); - //it->deleteLater(); - } - } - - // Set channel 10 to drums as per General MIDI's orders - if( chs[9].hasNotes && chs[9].it_inst && chs[9].isSF2 ) - { - // AFAIK, 128 should be the standard bank for drums in SF2. - // If not, this has to be made configurable. - chs[9].it_inst->childModel( "bank" )->setValue( 128 ); - chs[9].it_inst->childModel( "patch" )->setValue( 0 ); - } - - return true; -} - - - - -bool MidiImport::readRIFF( TrackContainer* tc ) -{ - // skip file length - skip( 4 ); - - // check file type ("RMID" = RIFF MIDI) - if( readID() != makeID( 'R', 'M', 'I', 'D' ) ) - { -invalid_format: - qWarning( "MidiImport::readRIFF(): invalid file format" ); - return false; - } - - // search for "data" chunk - while( 1 ) - { - const int id = readID(); - const int len = read32LE(); - if( file().atEnd() ) - { -data_not_found: - qWarning( "MidiImport::readRIFF(): data chunk not found" ); - return false; - } - if( id == makeID( 'd', 'a', 't', 'a' ) ) - { - break; - } - if( len < 0 ) - { - goto data_not_found; - } - skip( ( len + 1 ) & ~1 ); - } - - // the "data" chunk must contain data in SMF format - if( readID() != makeID( 'M', 'T', 'h', 'd' ) ) - { - goto invalid_format; - } - return readSMF( tc ); -} - - - - -void MidiImport::error() -{ - printf( "MidiImport::readTrack(): invalid MIDI data (offset %#x)\n", - (unsigned int) file().pos() ); -} - - - -extern "C" -{ - -// necessary for getting instance out of shared lib -Plugin * PLUGIN_EXPORT lmms_plugin_main( Model *, void * _data ) -{ - return new MidiImport( QString::fromUtf8( - static_cast( _data ) ) ); -} - - -} - diff --git a/plugins/MidiImport/portsmf/README.txt b/plugins/MidiImport/portsmf/README.txt deleted file mode 100644 index 0f36c9e4ee7..00000000000 --- a/plugins/MidiImport/portsmf/README.txt +++ /dev/null @@ -1,32 +0,0 @@ -portsmf README.txt -14 Jun 2008 -Roger B. Dannenberg - -Portsmf is "Port Standard MIDI File", a cross-platform, C++ library -for reading and writing Standard MIDI Files. - -License information: free and open source, see license.txt for details - -Features: - -- input and output of Standard MIDI Files -- data structures, classes, etc. for representing music data in memory - o sequence structure consisting of multiple tracks - o track structure consisting of multiple events - o events contain note and control data - o extensible attribute-value property lists - o tempo track and time signature representation -- input and output of a text-based representation: Allegro files -- extensive editing operations on sequences and tracks -- conversion to/from binary buffers for archiving, undo/redo, etc. - -Portsmf is a relatively small number of about 9 files, so there is -currently no support for building/maintaining Portsmf as a separate -library. (Contributions are welcome.) For now, it is suggested that -you simply compile these files along with your application sources. - -There is a test program in portsmf_test and makefiles to build it as -an example. - -You might want to browse through portsmf_test/allegro_test.cpp -for examples that use and exercise most of the portsmf functions. diff --git a/plugins/MidiImport/portsmf/algrd_internal.h b/plugins/MidiImport/portsmf/algrd_internal.h deleted file mode 100644 index 3b77adc4cd2..00000000000 --- a/plugins/MidiImport/portsmf/algrd_internal.h +++ /dev/null @@ -1,4 +0,0 @@ -/* algread_internal.h -- interface between allegro.cpp and allegrord.cpp */ - -Alg_error alg_read(std::istream &file, Alg_seq_ptr new_seq); - diff --git a/plugins/MidiImport/portsmf/algsmfrd_internal.h b/plugins/MidiImport/portsmf/algsmfrd_internal.h deleted file mode 100644 index 75cc0093bc7..00000000000 --- a/plugins/MidiImport/portsmf/algsmfrd_internal.h +++ /dev/null @@ -1,3 +0,0 @@ -/* algsmfrd_internal.h -- interface from allegrosmfrd.cpp to allegro.cpp */ - -Alg_error alg_smf_read(std::istream &file, Alg_seq_ptr new_seq); diff --git a/plugins/MidiImport/portsmf/allegro.cpp b/plugins/MidiImport/portsmf/allegro.cpp deleted file mode 100644 index 653ff4c07db..00000000000 --- a/plugins/MidiImport/portsmf/allegro.cpp +++ /dev/null @@ -1,2866 +0,0 @@ -// Allegro: music representation system, with -// extensible in-memory sequence structure -// upward compatible with MIDI -// implementations in C++ and Serpent -// external, text-based representation -// compatible with Aura -// -/* CHANGE LOG: -04 apr 03 -- fixed bug in add_track that caused infinite loop -*/ - -#include "debug.h" -#include "stdlib.h" -#include "stdio.h" -#include "string.h" -#include "memory.h" -#include -#include -using namespace std; -#include "allegro.h" -#include "algrd_internal.h" -#include "algsmfrd_internal.h" -// #include "trace.h" -- only needed for debugging -#include "math.h" - -#define STREQL(x, y) (strcmp(x, y) == 0) -#define MAX(x, y) ((x) > (y) ? (x) : (y)) - -// 4311 is type cast ponter to long warning -// 4996 is warning against strcpy -// 4267 is size_t to long warning -//#pragma warning(disable: 4311 4996 4267) -Alg_atoms symbol_table; -Serial_buffer Alg_track::ser_buf; // declare the static variable - -bool within(double d1, double d2, double epsilon) -{ - d1 -= d2; - return d1 < epsilon && d1 > -epsilon; -} - - -char *heapify(const char *s) -{ - char *h = new char[strlen(s) + 1]; - strcpy(h, s); - return h; -} - - -void Alg_atoms::expand() -{ - maxlen = (maxlen + 5); // extra growth for small sizes - maxlen += (maxlen >> 2); // add 25% - char **new_atoms = new Alg_attribute[maxlen]; - // now do copy - memcpy(new_atoms, atoms, len * sizeof(Alg_attribute)); - if (atoms) delete[] atoms; - atoms = new_atoms; -} - - -// insert_new -- insert an attribute name and type -// -// attributes are stored as a string consisting of the type -// (a char) followed by the attribute name. This makes it -// easy to retrieve the type or the name or both. -// -Alg_attribute Alg_atoms::insert_new(const char *name, char attr_type) -{ - if (len == maxlen) expand(); - char *h = new char[strlen(name) + 2]; - strcpy(h + 1, name); - *h = attr_type; - atoms[len++] = h; - return h; -} - - -Alg_attribute Alg_atoms::insert_attribute(Alg_attribute attr) -{ - for (int i = 0; i < len; i++) { - if (STREQL(attr, atoms[i])) { - return atoms[i]; - } - } - return insert_new(attr + 1, attr[0]); -} - - -Alg_attribute Alg_atoms::insert_string(const char *name) -{ - char attr_type = name[strlen(name) - 1]; - for (int i = 0; i < len; i++) { - if (attr_type == atoms[i][0] && - STREQL(name, atoms[i] + 1)) { - return atoms[i]; - } - } - return insert_new(name, attr_type); -} - - -void Alg_parameter::copy(Alg_parameter_ptr parm) -{ - *this = *parm; // copy all fields - // if the value is a string, copy the string - if (attr_type() == 's') { - s = heapify(s); - } -} - - -void Alg_parameter::show() -{ - switch (attr[0]) { - case 'r': - printf("%s:%g", attr_name(), r); - break; - case 's': - printf("%s:%s", attr_name(), s); - break; - case 'i': - printf("%s:%d", attr_name(), (int) i); - break; - case 'l': - printf("%s:%s", attr_name(), (l ? "t" : "f")); - break; - case 'a': - printf("%s:%s", attr_name(), a); - break; - } -} - - -Alg_parameter::~Alg_parameter() -{ - if (attr_type() == 's' && s) { - delete[] s; - } -} - - -void Alg_parameters::insert_real(Alg_parameters **list, char *name, double r) -{ - Alg_parameters_ptr a = new Alg_parameters(*list); - *list = a; - a->parm.set_attr(symbol_table.insert_string(name)); - a->parm.r = r; - assert(a->parm.attr_type() == 'r'); -} - - -void Alg_parameters::insert_string(Alg_parameters **list, char *name, char *s) -{ - Alg_parameters_ptr a = new Alg_parameters(*list); - *list = a; - a->parm.set_attr(symbol_table.insert_string(name)); - // string is deleted when parameter is deleted - a->parm.s = heapify(s); - assert(a->parm.attr_type() == 's'); -} - - -void Alg_parameters::insert_integer(Alg_parameters **list, char *name, long i) -{ - Alg_parameters_ptr a = new Alg_parameters(*list); - *list = a; - a->parm.set_attr(symbol_table.insert_string(name)); - a->parm.i = i; - assert(a->parm.attr_type() == 'i'); -} - - -void Alg_parameters::insert_logical(Alg_parameters **list, char *name, bool l) -{ - Alg_parameters_ptr a = new Alg_parameters(*list); - *list = a; - a->parm.set_attr(symbol_table.insert_string(name)); - a->parm.l = l; - assert(a->parm.attr_type() == 'l'); -} - - -void Alg_parameters::insert_atom(Alg_parameters **list, char *name, char *s) -{ - Alg_parameters_ptr a = new Alg_parameters(*list); - *list = a; - a->parm.set_attr(symbol_table.insert_string(name)); - a->parm.a = symbol_table.insert_string(s); - assert(a->parm.attr_type() == 'a'); -} - - -Alg_parameters *Alg_parameters::remove_key(Alg_parameters **list, const char *name) -{ - while (*list) { - if (STREQL((*list)->parm.attr_name(), name)) { - Alg_parameters_ptr p = *list; - *list = p->next; - p->next = NULL; - return p; // caller should free this pointer - } - list = &((*list)->next); - } - return NULL; -} - - -Alg_parameter_ptr Alg_parameters::find(Alg_attribute *attr) -{ - assert(attr); - Alg_parameters_ptr temp = this; - while (temp) { - if (temp->parm.attr == *attr) { - return &(temp->parm); - } - } - return NULL; -} - - -int Alg_event::get_type_code() -{ - if (!is_note()) { - const char* attr = get_attribute(); - if (STREQL(attr, "gate")) // volume change - return ALG_GATE; - if (STREQL(attr, "bend")) // pitch bend - return ALG_BEND; - if (strncmp(attr, "control", 7) == 0) // control change - // note that midi control changes have attributes of the form - // "control" where n is the decimal number (as a character string) - // of the midi controller, e.g. control2 is the breath controller. - // We don't check for decimal numbers in the range 0-127, so any - // attribute that begins with "control" is an ALG_CONTROL: - return ALG_CONTROL; - if (STREQL(attr, "program")) // program change - return ALG_PROGRAM; - if (STREQL(attr, "pressure")) // pressure change - return ALG_PRESSURE; - if (STREQL(attr, "keysig")) // key signature - return ALG_KEYSIG; - if (STREQL(attr, "timesig_num")) // time signature numerator - return ALG_TIMESIG_NUM; - if (STREQL(attr, "timesig_den")) // time signature denominator - return ALG_TIMESIG_DEN; - return ALG_OTHER; - } - return ALG_NOTE; // it is a note -} - - -void Alg_event::set_parameter(Alg_parameter_ptr new_parameter) -{ - Alg_parameter_ptr parm; - if (is_note()) { - Alg_note_ptr note = (Alg_note_ptr) this; - parm = note->parameters->find(&(new_parameter->attr)); - if (!parm) { - note->parameters = new Alg_parameters(note->parameters); - parm = &(note->parameters->parm); - } - } else { // update - Alg_update_ptr update = (Alg_update_ptr) this; - parm = &(update->parameter); - } - parm->copy(new_parameter); // copy entire parameter -} - - -void Alg_event::set_string_value(char *a, char *value) -{ - assert(a); // must be non-null - Alg_attribute attr = symbol_table.insert_string(a); - assert(attr[0] == 's'); - Alg_parameter parm; - parm.set_attr(attr); - parm.s = value; - set_parameter(&parm); - parm.s = NULL; // do this to prevent string from being freed -} - - -void Alg_event::set_real_value(char *a, double value) -{ - assert(a); // must be non-null - // attr is like a, but it has the type code prefixed for - // fast lookup, and it is a unique string in symbol_table - // e.g. a="attackr" -> attr="rattackr" - Alg_attribute attr = symbol_table.insert_string(a); - assert(attr[0] == 'r'); - Alg_parameter parm; - parm.set_attr(attr); - parm.r = value; - set_parameter(&parm); - // since type is 'r' we don't have to NULL the string -} - - -void Alg_event::set_logical_value(char *a, bool value) -{ - assert(a); // must be non-null - Alg_attribute attr = symbol_table.insert_string(a); - assert(attr[0] == 'l'); - Alg_parameter parm; - parm.set_attr(attr); - parm.l = value; - set_parameter(&parm); - // since type is 'l' we don't have to NULL the string -} - - -void Alg_event::set_integer_value(char *a, long value) -{ - assert(a); // must be non-null - Alg_attribute attr = symbol_table.insert_string(a); - assert(attr[0] == 'i'); - Alg_parameter parm; - parm.set_attr(attr); - parm.i = value; - set_parameter(&parm); - // since tpye is 'i' we don't have to NULL the string -} - - -void Alg_event::set_atom_value(char *a, char *value) -{ - assert(a); // must be non-null - Alg_attribute attr = symbol_table.insert_string(a); - assert(attr[0] == 'a'); - Alg_parameter parm; - parm.set_attr(attr); - parm.a = value; - set_parameter(&parm); - /* since type is 'a' we don't have to null the string */ -} - - -float Alg_event::get_pitch() -{ - assert(is_note()); - Alg_note* note = (Alg_note *) this; - return note->pitch; -} - - -float Alg_event::get_loud() -{ - assert(is_note()); - Alg_note* note = (Alg_note *) this; - return note->loud; -} - - -double Alg_event::get_start_time() -{ - assert(is_note()); - Alg_note* note = (Alg_note *) this; - return note->time; -} - - -double Alg_event::get_end_time() -{ - assert(is_note()); - Alg_note* note = (Alg_note *) this; - return note->time + note->dur; -} - - -double Alg_event::get_duration() -{ - assert(is_note()); - Alg_note* note = (Alg_note *) this; - return note->dur; -} - - -void Alg_event::set_pitch(float p) -{ - assert(is_note()); - Alg_note* note = (Alg_note *) this; - note->pitch = p; -} - -void Alg_event::set_loud(float l) -{ - assert(is_note()); - Alg_note *note = (Alg_note *) this; - note->loud = l; -} - - -void Alg_event::set_duration(double d) -{ - assert(is_note()); - Alg_note* note = (Alg_note *) this; - note->dur = d; -} - - -bool Alg_event::has_attribute(char *a) -{ - assert(is_note()); - assert(a); // must be non-null - Alg_note* note = (Alg_note *) this; - Alg_attribute attr = symbol_table.insert_string(a); - Alg_parameter_ptr parm = note->parameters->find(&attr); - return parm != NULL; -} - - -char Alg_event::get_attribute_type(char *a) -{ - assert(is_note()); - assert(a); - return a[strlen(a) - 1]; -} - - -char *Alg_event::get_string_value(char *a, char *value) -{ - assert(is_note()); - assert(a); // must be non-null - Alg_note* note = (Alg_note *) this; - Alg_attribute attr = symbol_table.insert_string(a); - assert(a[0] == 's'); // must be of type string - Alg_parameter_ptr parm = note->parameters->find(&attr); - if (parm) return parm->s; - return value; -} - - -double Alg_event::get_real_value(char *a, double value) -{ - assert(is_note()); - assert(a); - Alg_note* note = (Alg_note *) this; - Alg_attribute attr = symbol_table.insert_string(a); - assert(a[0] == 'r'); // must be of type real - Alg_parameter_ptr parm = note->parameters->find(&attr); - if (parm) return parm->r; - return value; -} - - -bool Alg_event::get_logical_value(char *a, bool value) -{ - assert(is_note()); - assert(a); - Alg_note* note = (Alg_note *) this; - Alg_attribute attr = symbol_table.insert_string(a); - assert(a[0] == 'l'); // must be of type logical - Alg_parameter_ptr parm = note->parameters->find(&attr); - if (parm) return parm->l; - return value; -} - - -long Alg_event::get_integer_value(char *a, long value) -{ - assert(is_note()); - assert(a); - Alg_note* note = (Alg_note *) this; - Alg_attribute attr = symbol_table.insert_string(a); - assert(a[0] == 'i'); // must be of type integer - Alg_parameter_ptr parm = note->parameters->find(&attr); - if (parm) return parm->i; - return value; -} - - -char *Alg_event::get_atom_value(char *a, char *value) -{ - assert(is_note()); - assert(a); - Alg_note* note = (Alg_note *) this; - Alg_attribute attr = symbol_table.insert_string(a); - assert(a[0] == 'a'); // must be of type atom - Alg_parameter_ptr parm = note->parameters->find(&attr); - if (parm) return parm->a; - // if default is a string, convert to an atom (unique - // string in symbol table) and return it - return (value == NULL ? NULL : - symbol_table.insert_string(value)); -} - - -void Alg_event::delete_attribute(char *a) -{ - assert(is_note()); - Alg_note* note = (Alg_note *) this; - Alg_parameters::remove_key(&(note->parameters), a); -} - - -const char *Alg_event::get_attribute() -// Note: this returns a string, not an Alg_attribute -{ - assert(is_update()); - Alg_update* update = (Alg_update *) this; - return update->parameter.attr_name(); -} - - -char Alg_event::get_update_type() -{ - assert(is_update()); - Alg_update* update = (Alg_update *) this; - return update->parameter.attr_type(); -} - - -char *Alg_event::get_string_value() -{ - assert(is_update()); - Alg_update* update = (Alg_update *) this; - assert(get_update_type() == 's'); - return update->parameter.a; -} - - -double Alg_event::get_real_value() -{ - assert(is_update()); - Alg_update* update = (Alg_update *) this; - assert(get_update_type() == 'r'); - return update->parameter.r; -} - - -bool Alg_event::get_logical_value() -{ - assert(is_update()); - Alg_update* update = (Alg_update *) this; - assert(get_update_type() == 'l'); - return update->parameter.l; -} - - -long Alg_event::get_integer_value() -{ - assert(is_update()); - Alg_update* update = (Alg_update *) this; - assert(get_update_type() == 'i'); - return update->parameter.i; -} - - -char *Alg_event::get_atom_value() -{ - assert(is_update()); - Alg_update* update = (Alg_update *) this; - assert(get_update_type() == 'a'); - return update->parameter.a; -} - - -bool Alg_event::overlap(double t, double len, bool all) -{ - // event starts within region - if (time >= t && time <= t + len - ALG_EPS) - return true; - if (all && is_note()) { - double dur = ((Alg_note_ptr) this)->dur; - // note ends within region - if (time < t && time + dur - ALG_EPS > t) - return true; - } - // does not overlap - return false; -} - - -Alg_note::Alg_note(Alg_note_ptr note) -{ - *this = *note; // copy all fields - // parameters is now a shared pointer. We need to copy the - // parameters - Alg_parameters_ptr next_param_ptr = parameters; - while (next_param_ptr) { - Alg_parameters_ptr new_params = new Alg_parameters(next_param_ptr->next); - new_params->parm.copy(&(next_param_ptr->parm)); // copy the attribute and value - next_param_ptr = new_params->next; - } -} - - -Alg_note::~Alg_note() -{ - while (parameters) { - Alg_parameters_ptr to_delete = parameters; - parameters = parameters->next; - delete to_delete; - } -} - - -void Alg_note::show() -{ - printf("Alg_note: time %g, chan %d, dur %g, key %d, " - "pitch %g, loud %g, attributes ", - time, (int) chan, dur, (int) key, pitch, loud); - Alg_parameters_ptr parms = parameters; - while (parms) { - parms->parm.show(); - printf(" "); - parms = parms->next; - } - printf("\n"); -} - - -Alg_update::Alg_update(Alg_update_ptr update) -{ - *this = *update; // copy all fields - // parameter requires careful copy to possibly duplicate string value: - this->parameter.copy(&(update->parameter)); -} - - -void Alg_update::show() -{ - printf("Alg_update: "); - parameter.show(); - printf("\n"); -} - - -void Alg_events::expand() -{ - maxlen = (maxlen + 5); // extra growth for small sizes - maxlen += (maxlen >> 2); // add 25% - Alg_event_ptr *new_events = new Alg_event_ptr[maxlen]; - // now do copy - memcpy(new_events, events, len * sizeof(Alg_event_ptr)); - if (events) delete[] events; - events = new_events; -} - - -void Alg_events::insert(Alg_event_ptr event) -{ - if (maxlen <= len) { - expand(); - } - // Note: if the new event is the last one, the assignment - // events[i] = event; (below) will never execute, so just - // in case, we do the assignment here. events[len] will - // be replaced during the memmove() operation below if - // this is not the last event. - events[len] = event; - len++; - // find insertion point: (this could be a binary search) - for (int i = 0; i < len; i++) { - if (events[i]->time > event->time) { - // insert event at i - memmove(&events[i + 1], &events[i], - sizeof(Alg_event_ptr) * (len - i - 1)); - events[i] = event; - return; - } - } -} - -Alg_event_ptr Alg_events::uninsert(long index) -{ - assert(0 <= index && index < len); - Alg_event_ptr event = events[index]; - memmove(events + index, events + index + 1, - sizeof(Alg_event_ptr) * (len - index - 1)); - len--; - return event; -} - - -void Alg_events::append(Alg_event_ptr event) -{ - if (maxlen <= len) { - expand(); - } - events[len++] = event; - // keep track of last note_off time - if (event->is_note()) { - Alg_note_ptr note = (Alg_note_ptr) event; - double note_off = note->time + note->dur; - if (note_off > last_note_off) - last_note_off = note_off; - } -} - - -Alg_events::~Alg_events() -{ - // individual events are not deleted, only the array - if (events) { - delete[] events; - } -} - - -Alg_event_list::Alg_event_list(Alg_track *owner) -{ - events_owner = owner; - sequence_number = owner->sequence_number; - beat_dur = 0.0; real_dur = 0.0; type = 'e'; -} - - -Alg_event_ptr &Alg_event_list::operator [](int i) -{ - assert(i >= 0 && i < len); - return events[i]; -} - - -Alg_event_list::~Alg_event_list() -{ - // note that the events contained in the list are not destroyed -} - - -void Alg_event_list::set_start_time(Alg_event *event, double t) -{ - // For Alg_event_list, find the owner and do the update there - // For Alg_track, change the time and move the event to the right place - // For Alg_seq, find the track and do the update there - - long index = 0, i; - Alg_track_ptr track_ptr = Alg_track_ptr(); - if (type == 'e') { // this is an Alg_event_list - // make sure the owner has not changed its event set - assert(events_owner && - sequence_number == events_owner->sequence_number); - // do the update on the owner - events_owner->set_start_time(event, t); - return; - } else if (type == 't') { // this is an Alg_track - // find the event in the track - track_ptr = (Alg_track_ptr) this; - // this should be a binary search since events are in time order - // probably there should be member function to do the search - for (index = 0; index < length(); index++) { - if ((*track_ptr)[index] == event) goto found_event; - } - } else { // type == 's', an Alg_seq - Alg_seq_ptr seq = (Alg_seq_ptr) this; - for (i = 0; i < seq->tracks(); i++) { - track_ptr = seq->track(i); - // if you implemented binary search, you could call it - // instead of this loop too. - for (index = 0; index < track_ptr->length(); index++) { - if ((*track_ptr)[index] == event) goto found_event; - } - } - } - assert(false); // event not found seq or track! - found_event: - // at this point, track[index] == event - // we could be clever and figure out exactly what notes to move - // but it is simpler to just remove the event and reinsert it: - track_ptr->uninsert(index); - event->time = t; - track_ptr->insert(event); -} - - -void Alg_beats::expand() -{ - maxlen = (maxlen + 5); // extra growth for small sizes - maxlen += (maxlen >> 2); // add 25% - Alg_beat_ptr new_beats = new Alg_beat[maxlen]; - // now do copy - memcpy(new_beats, beats, len * sizeof(Alg_beat)); - if (beats) delete[] beats; - beats = new_beats; -} - - -void Alg_beats::insert(long i, Alg_beat_ptr beat) -{ - assert(i >= 0 && i <= len); - if (maxlen <= len) { - expand(); - } - memmove(&beats[i + 1], &beats[i], sizeof(Alg_beat) * (len - i)); - memcpy(&beats[i], beat, sizeof(Alg_beat)); - len++; -} - - -Alg_time_map::Alg_time_map(Alg_time_map *map) -{ - refcount = 0; - assert(map->beats[0].beat == 0 && map->beats[0].time == 0); - assert(map->beats.len > 0); - // new_beats[0] = map->beats[0]; - // this is commented because - // both new_beats[0] and map->beats[0] should be (0, 0) - for (int i = 1; i < map->beats.len; i++) { - beats.insert(i, &map->beats[i]); - } - last_tempo = map->last_tempo; - last_tempo_flag = map->last_tempo_flag; -} - - -void Alg_time_map::show() -{ - printf("Alg_time_map: "); - for (int i = 0; i < beats.len; i++) { - Alg_beat &b = beats[i]; - printf("(%g, %g) ", b.time, b.beat); - } - printf("last tempo: %g\n", last_tempo); -} - - -long Alg_time_map::locate_time(double time) -{ - int i = 0; - while ((i < beats.len) && (time > beats[i].time)) { - i++; - } - return i; -} - - -long Alg_time_map::locate_beat(double beat) -{ - int i = 0; - while ((i < beats.len) && (beat > beats[i].beat)) { - i++; - } - return i; -} - - -double Alg_time_map::beat_to_time(double beat) -{ - Alg_beat_ptr mbi; - Alg_beat_ptr mbi1; - if (beat <= 0) { - return beat; - } - int i = locate_beat(beat); - if (i == beats.len) { - if (last_tempo_flag) { - return beats[i - 1].time + - (beat - beats[i - 1].beat) / last_tempo; - } else if (i == 1) { - return beat * 60.0 / ALG_DEFAULT_BPM; - // so we use that as default allegro tempo too - } else { - mbi = &beats[i - 2]; - mbi1 = &beats[i - 1]; - } - } else { - mbi = &beats[i - 1]; - mbi1 = &beats[i]; - } - // whether w extrapolate or interpolate, the math is the same - double time_dif = mbi1->time - mbi->time; - double beat_dif = mbi1->beat - mbi->beat; - return mbi->time + (beat - mbi->beat) * time_dif / beat_dif; -} - - -double Alg_time_map::time_to_beat(double time) -{ - Alg_beat_ptr mbi; - Alg_beat_ptr mbi1; - if (time <= 0.0) return time; - int i = locate_time(time); - if (i == beats.len) { - if (last_tempo_flag) { - return beats[i - 1].beat + - (time - beats[i - 1].time) * last_tempo; - } else if (i == 1) { - return time * (ALG_DEFAULT_BPM / 60.0); - } else { - mbi = &beats[i - 2]; - mbi1 = &beats[i - 1]; - } - } else { - mbi = &beats[i - 1]; - mbi1 = & beats[i]; - } - double time_dif = mbi1->time - mbi->time; - double beat_dif = mbi1->beat - mbi->beat; - return mbi->beat + (time - mbi->time) * beat_dif / time_dif; -} - - -void Alg_time_map::insert_beat(double time, double beat) -{ - int i = locate_time(time); // i is insertion point - if (i < beats.len && within(beats[i].time, time, 0.000001)) { - // replace beat if time is already in the map - beats[i].beat = beat; - } else { - Alg_beat point; - point.beat = beat; - point.time = time; - beats.insert(i, &point); - } - // beats[i] contains new beat - // make sure we didn't generate a zero tempo. - // if so, space beats by one microbeat as necessary - long j = i; - if (j == 0) j = 1; // do not adjust beats[0] - while (j < beats.len && - beats[j - 1].beat + 0.000001 >= beats[j].beat) { - beats[j].beat = beats[j - 1].beat + 0.000001; - j++; - } -} - - -bool Alg_time_map::insert_tempo(double tempo, double beat) -{ - tempo = tempo / 60.0; // convert to beats per second - // change the tempo at the given beat until the next beat event - if (beat < 0) return false; - double time = beat_to_time(beat); - long i = locate_time(time); - if (i >= beats.len || !within(beats[i].time, time, 0.000001)) { - insert_beat(time, beat); - } - // now i is index of beat where tempo will change - if (i == beats.len - 1) { - last_tempo = tempo; - // printf("last_tempo to %g\n", last_tempo); - last_tempo_flag = true; - } else { // adjust all future beats - // compute the difference in beats - double diff = beats[i + 1].beat - beats[i].beat; - // convert beat difference to seconds at new tempo - diff = diff / tempo; - // figure out old time difference: - double old_diff = beats[i + 1].time - time; - // compute difference too - diff = diff - old_diff; - // apply new_diff to score and beats - while (i < beats.len) { - beats[i].time = beats[i].time + diff; - i++; - } - } - return true; -} - - -bool Alg_time_map::set_tempo(double tempo, double start_beat, double end_beat) -{ - if (start_beat >= end_beat) return false; - // algorithm: insert a beat event if necessary at start_beat - // and at end_beat - // delete intervening map elements - // change the tempo - insert_beat(beat_to_time(start_beat), start_beat); - insert_beat(beat_to_time(end_beat), end_beat); - long start_x = locate_beat(start_beat) + 1; - long stop_x = locate_beat(end_beat); - while (stop_x < beats.len) { - beats[start_x] = beats[stop_x]; - start_x++; - stop_x++; - } - beats.len = start_x; // truncate the map to new length - return insert_tempo(tempo, start_beat); -} - - -void Alg_time_map::trim(double start, double end, bool units_are_seconds) -{ - // extract the time map from start to end and shift to time zero - // start and end are time in seconds if units_are_seconds is true - int i = 0; // index into beats - int start_index; // index of first breakpoint after start - int count = 1; - double initial_beat = start; - double final_beat = end; - if (units_are_seconds) { - initial_beat = time_to_beat(start); - final_beat = time_to_beat(end); - } else { - start = beat_to_time(initial_beat); - end = beat_to_time(final_beat); - } - while (i < length() && beats[i].time < start) i++; - // now i is index into beats of the first breakpoint after start - // beats[0] is (0,0) and remains that way - // copy beats[start_index] to beats[1], etc. - // skip any beats at or near (start,initial_beat), using count - // to keep track of how many entries there are - start_index = i; - while (i < length() && beats[i].time < end) { - if (beats[i].time - start > ALG_EPS && - beats[i].beat - initial_beat > ALG_EPS) { - beats[i].time = beats[i].time - start; - beats[i].beat = beats[i].beat - initial_beat; - beats[i - start_index + 1] = beats[i]; - count = count + 1; - } else { - start_index = start_index + 1; - } - i = i + 1; - } - // set last tempo data - // we last examined beats[i-1] and copied it to - // beats[i - start_index]. Next tempo should come - // from beats[i] and store in beats[i - start_index + 1] - // case 1: there is at least one breakpoint beyond end - // => interpolate to put a breakpoint at end - // case 2: no more breakpoints => set last tempo data - if (i < length()) { - // we know beats[i].time >= end, so case 1 applies - beats[i - start_index + 1].time = end - start; - beats[i - start_index + 1].beat = final_beat - initial_beat; - count = count + 1; - } - // else we'll just use stored last tempo (if any) - beats.len = count; -} - - -void Alg_time_map::cut(double start, double len, bool units_are_seconds) -{ - // remove portion of time map from start to start + len, - // shifting the tail left by len. start and len are in whatever - // units the score is in. If you cut the time_map as well as cut - // the tracks of the sequence, then sequences will preserve the - // association between tempo changes and events - double end = start + len; - double initial_beat = start; - double final_beat = end; - int i = 0; - - if (units_are_seconds) { - initial_beat = time_to_beat(start); - final_beat = time_to_beat(end); - } else { - start = beat_to_time(initial_beat); - end = beat_to_time(final_beat); - len = end - start; - } - double beat_len = final_beat - initial_beat; - - while (i < length() && beats[i].time < start - ALG_EPS) { - i = i + 1; - } - - // if no beats exist at or after start, just return; nothing to cut - if (i == length()) return; - - // now i is index into beats of the first breakpoint on or - // after start, insert (start, initial_beat) in map - if (i < length() && within(beats[i].time, start, ALG_EPS)) { - // perterb time map slightly (within alg_eps) to place - // break point exactly at the start time - beats[i].time = start; - beats[i].beat = initial_beat; - } else { - Alg_beat point(start, initial_beat); - beats.insert(i, &point); - } - // now, we're correct up to beats[i] and beats[i] happens at start. - // find first beat after end so we can start shifting from there - i = i + 1; - int start_index = i; - while (i < length() && beats[i].time < end + ALG_EPS) i++; - // now beats[i] is the next point to be included in beats - // but from i onward, we must shift by (-len, -beat_len) - while (i < length()) { - Alg_beat &b = beats[i]; - b.time = b.time - len; - b.beat = b.beat - beat_len; - beats[start_index] = b; - i = i + 1; - start_index = start_index + 1; - } - beats.len = start_index; -} - - -void Alg_time_map::paste(double beat, Alg_track *tr) -{ - // insert a given time map at a given time and dur (in beats) - Alg_time_map_ptr from_map = tr->get_time_map(); - // printf("time map paste\nfrom map\n"); - // from_map->show(); - // printf("to map\n"); - // show(); - Alg_beats &from = from_map->beats; - double time = beat_to_time(beat); - // Locate the point at which dur occurs - double dur = tr->get_beat_dur(); - double tr_end_time = from_map->beat_to_time(dur); - // add offset to make room for insert - int i = locate_beat(beat); - while (i < length()) { - beats[i].beat += dur; - beats[i].time += tr_end_time; - i++; - } - // printf("after opening up\n"); - // show(); - // insert point at beginning and end of paste - insert_beat(time, beat); - // printf("after beginning point insert\n"); - // show(); - // insert_beat(time + tr_end_time, beat + dur); - // printf("after ending point insert\n"); - // show(); - int j = from_map->locate_beat(dur); - for (i = 0; i < j; i++) { - insert_beat(from[i].time + time, // shift by time - from[i].beat + beat); // and beat - } - // printf("after inserts\n"); - show(); -} - - -void Alg_time_map::insert_time(double start, double len) -{ - // find time,beat pair that determines tempo at start - // compute beat offset = (delta beat / delta time) * len - // add len,beat offset to each following Alg_beat - // show(); - int i = locate_time(start); // start <= beats[i].time - if (beats[i].time == start) i++; // start < beats[i].time - // case 1: between beats - if (i > 0 && i < length()) { - double beat_offset = len * (beats[i].beat - beats[i-1].beat) / - (beats[i].time - beats[i-1].time); - while (i < length()) { - beats[i].beat += beat_offset; - beats[i].time += len; - i++; - } - } // otherwise, last tempo is in effect; nothing to do - // printf("time_map AFTER INSERT\n"); - // show(); -} - - -void Alg_time_map::insert_beats(double start, double len) -{ - int i = locate_beat(start); // start <= beats[i].beat - if (beats[i].beat == start) i++; - if (i > 0 && i < length()) { - double time_offset = len * (beats[i].time - beats[i-1].time) / - (beats[i].beat - beats[i-1].beat); - while (i < length()) { - beats[i].time += time_offset; - beats[i].beat += len; - i++; - } - } // otherwise, last tempo is in effect; nothing to do - // printf("time_map AFTER INSERT\n"); - // show(); -} - - -Alg_track::Alg_track(Alg_time_map *map, bool seconds) -{ - type = 't'; - time_map = NULL; - units_are_seconds = seconds; - set_time_map(map); -} - - -Alg_event_ptr Alg_track::copy_event(Alg_event_ptr event) -{ - Alg_event *new_event; - if (event->is_note()) { - new_event = new Alg_note((Alg_note_ptr) event); - } else { // update - new_event = new Alg_update((Alg_update_ptr) event); - } - return new_event; -} - - -Alg_track::Alg_track(Alg_track &track) -{ - type = 't'; - time_map = NULL; - for (int i = 0; i < track.length(); i++) { - append(copy_event(track.events[i])); - } - set_time_map(track.time_map); - units_are_seconds = track.units_are_seconds; -} - - -Alg_track::Alg_track(Alg_event_list_ref event_list, Alg_time_map_ptr map, - bool units_are_seconds) -{ - type = 't'; - time_map = NULL; - for (int i = 0; i < event_list.length(); i++) { - append(copy_event(event_list[i])); - } - set_time_map(map); - this->units_are_seconds = units_are_seconds; -} - - -void Alg_track::serialize(void **buffer, long *bytes) -{ - // first determine whether this is a seq or a track. - // if it is a seq, then we will write the time map and a set of tracks - // if it is a track, we just write the track data and not the time map - // - // The code will align doubles on ALIGN boundaries, and longs and - // floats are aligned to multiples of 4 bytes. - // - // The format for a seq is: - // 'ALGS' -- indicates that this is a sequence - // long length of all seq data in bytes starting with 'ALGS' - // long channel_offset_per_track - // long units_are_seconds - // time_map: - // double last_tempo - // long last_tempo_flag - // long len -- number of tempo changes - // for each tempo change (Alg_beat): - // double time - // double beat - // time_sigs: - // long len -- number of time_sigs - // long pad - // for each time signature: - // double beat - // double num - // double den - // tracks: - // long len -- number of tracks - // long pad - // for each track: - // 'ALGT' -- indicates this is a track - // long length of all track data in bytes starting with 'ALGT' - // long units_are_seconds - // double beat_dur - // double real_dur - // long len -- number of events - // for each event: - // long selected - // long type - // long key - // long channel - // double time - // if this is a note: - // double pitch - // double dur - // double loud - // long len -- number of parameters - // for each parameter: - // char attribute[] with zero pad to ALIGN - // one of the following, depending on type: - // double r - // char s[] terminated by zero - // long i - // long l - // char a[] terminated by zero - // zero pad to ALIGN - // else if this is an update - // (same representation as parameter above) - // zero pad to ALIGN - // - // The format for a track is given within the Seq format above - assert(get_type() == 't'); - ser_buf.init_for_write(); - serialize_track(); - *buffer = ser_buf.to_heap(bytes); -} - - -void Alg_seq::serialize(void **buffer, long *bytes) -{ - assert(get_type() == 's'); - ser_buf.init_for_write(); - serialize_seq(); - *buffer = ser_buf.to_heap(bytes); -} - - -void Serial_buffer::check_buffer(long needed) -{ - if (len < (ptr - buffer) + needed) { // do we need more space? - long new_len = len * 2; // exponential growth is important - // initially, length is zero, so bump new_len to a starting value - if (new_len == 0) new_len = 1024; - // make sure new_len is as big as needed - if (needed > new_len) new_len = needed; - char *new_buffer = new char[new_len]; // allocate space - memcpy(new_buffer, buffer, len); // copy from old buffer - ptr = new_buffer + (ptr - buffer); // relocate ptr to new buffer - delete buffer; // free old buffer - buffer = new_buffer; // update buffer information - len = new_len; - } -} - - -void Alg_seq::serialize_seq() -{ - int i; // loop counters - // we can easily compute how much buffer space we need until we - // get to tracks, so expand at least that much - long needed = 48 + 16 * time_map->beats.len + 24 * time_sig.length(); - ser_buf.check_buffer(needed); - ser_buf.set_char('A'); - ser_buf.set_char('L'); - ser_buf.set_char('G'); - ser_buf.set_char('S'); - long length_offset = ser_buf.get_posn(); - ser_buf.set_int32(0); // leave room to come back and write length - ser_buf.set_int32(channel_offset_per_track); - ser_buf.set_int32(units_are_seconds); - ser_buf.set_double(time_map->last_tempo); - ser_buf.set_int32(time_map->last_tempo_flag); - ser_buf.set_int32(time_map->beats.len); - for (i = 0; i < time_map->beats.len; i++) { - ser_buf.set_double(time_map->beats[i].time); - ser_buf.set_double(time_map->beats[i].beat); - } - ser_buf.set_int32(time_sig.length()); - ser_buf.pad(); - for (i = 0; i < time_sig.length(); i++) { - ser_buf.set_double(time_sig[i].beat); - ser_buf.set_double(time_sig[i].num); - ser_buf.set_double(time_sig[i].den); - } - ser_buf.set_int32(tracks()); - ser_buf.pad(); - for (i = 0; i < tracks(); i++) { - track(i)->serialize_track(); - } - // do not include ALGS, include padding at end - ser_buf.store_long(length_offset, ser_buf.get_posn() - length_offset); -} - - -void Alg_track::serialize_track() -{ - // to simplify the code, copy from parameter addresses to locals - int j; - ser_buf.check_buffer(32); - ser_buf.set_char('A'); - ser_buf.set_char('L'); - ser_buf.set_char('G'); - ser_buf.set_char('T'); - long length_offset = ser_buf.get_posn(); // save location for track length - ser_buf.set_int32(0); // room to write track length - ser_buf.set_int32(units_are_seconds); - ser_buf.set_double(beat_dur); - ser_buf.set_double(real_dur); - ser_buf.set_int32(len); - for (j = 0; j < len; j++) { - ser_buf.check_buffer(24); - Alg_event *event = (*this)[j]; - ser_buf.set_int32(event->get_selected()); - ser_buf.set_int32(event->get_type()); - ser_buf.set_int32(event->get_identifier()); - ser_buf.set_int32(event->chan); - ser_buf.set_double(event->time); - if (event->is_note()) { - ser_buf.check_buffer(20); - Alg_note *note = (Alg_note *) event; - ser_buf.set_float(note->pitch); - ser_buf.set_float(note->loud); - ser_buf.set_double(note->dur); - long parm_num_offset = ser_buf.get_posn(); - long parm_num = 0; - ser_buf.set_int32(0); // placeholder for no. parameters - Alg_parameters_ptr parms = note->parameters; - while (parms) { - serialize_parameter(&(parms->parm)); - parms = parms->next; - parm_num++; - } - ser_buf.store_long(parm_num_offset, parm_num); - } else { - assert(event->is_update()); - Alg_update *update = (Alg_update *) event; - serialize_parameter(&(update->parameter)); - } - ser_buf.check_buffer(7); // maximum padding possible - ser_buf.pad(); - } - // write length, not including ALGT, including padding at end - ser_buf.store_long(length_offset, ser_buf.get_posn() - length_offset); -} - - -void Alg_track::serialize_parameter(Alg_parameter *parm) -{ - // add eight to account for name + zero end-of-string and the - // possibility of adding 7 padding bytes - long len = strlen(parm->attr_name()) + 8; - ser_buf.check_buffer(len); - ser_buf.set_string(parm->attr_name()); - ser_buf.pad(); - switch (parm->attr_type()) { - case 'r': - ser_buf.check_buffer(8); - ser_buf.set_double(parm->r); - break; - case 's': - ser_buf.check_buffer(strlen(parm->s) + 1); - ser_buf.set_string(parm->s); - break; - case 'i': - ser_buf.check_buffer(4); - ser_buf.set_int32(parm->i); - break; - case 'l': - ser_buf.check_buffer(4); - ser_buf.set_int32(parm->l); - break; - case 'a': - ser_buf.check_buffer(strlen(parm->a) + 1); - ser_buf.set_string(parm->a); - break; - } -} - - - -Alg_track *Alg_track::unserialize(void *buffer, long len) -{ - assert(len > 8); - ser_buf.init_for_read(buffer, len); - bool alg = ser_buf.get_char() == 'A' && - ser_buf.get_char() == 'L' && - ser_buf.get_char() == 'G'; - assert(alg); - char c = ser_buf.get_char(); - if (c == 'S') { - Alg_seq *seq = new Alg_seq; - seq->unserialize_seq(); - return seq; - } else { - assert(c == 'T'); - Alg_track *track = new Alg_track; - track->unserialize_track(); - return track; - } -} - - -void Alg_seq::unserialize_seq() -{ - ser_buf.check_input_buffer(28); - long len = ser_buf.get_int32(); - assert(ser_buf.get_len() >= len); - channel_offset_per_track = ser_buf.get_int32(); - units_are_seconds = (bool) ser_buf.get_int32(); - time_map = new Alg_time_map(); - time_map->last_tempo = ser_buf.get_double(); - time_map->last_tempo_flag = (bool) ser_buf.get_int32(); - long beats = ser_buf.get_int32(); - ser_buf.check_input_buffer(beats * 16 + 4); - int i; - for (i = 0; i < beats; i++) { - double time = ser_buf.get_double(); - double beat = ser_buf.get_double(); - time_map->insert_beat(time, beat); - // printf("time_map: %g, %g\n", time, beat); - } - long time_sig_len = ser_buf.get_int32(); - ser_buf.get_pad(); - ser_buf.check_input_buffer(time_sig_len * 24 + 8); - for (i = 0; i < time_sig_len; i++) { - double beat = ser_buf.get_double(); - double num = ser_buf.get_double(); - double den = ser_buf.get_double(); - time_sig.insert(beat, num, den); - } - long tracks_num = ser_buf.get_int32(); - ser_buf.get_pad(); - add_track(tracks_num - 1); // create tracks_num tracks - for (i = 0; i < tracks_num; i++) { - track(i)->unserialize_track(); - } - // assume seq started at beginning of buffer. len measures - // bytes after 'ALGS' header, so add 4 bytes and compare to - // current buffer position -- they should agree - assert(ser_buf.get_posn() == len + 4); -} - - -void Alg_track::unserialize_track() -{ - ser_buf.check_input_buffer(32); - assert(ser_buf.get_char() == 'A'); - assert(ser_buf.get_char() == 'L'); - assert(ser_buf.get_char() == 'G'); - assert(ser_buf.get_char() == 'T'); - long offset = ser_buf.get_posn(); // stored length does not include 'ALGT' - long bytes = ser_buf.get_int32(); - assert(bytes <= ser_buf.get_len() - offset); - units_are_seconds = (bool) ser_buf.get_int32(); - beat_dur = ser_buf.get_double(); - real_dur = ser_buf.get_double(); - int event_count = ser_buf.get_int32(); - for (int i = 0; i < event_count; i++) { - ser_buf.check_input_buffer(24); - long selected = ser_buf.get_int32(); - char type = (char) ser_buf.get_int32(); - long key = ser_buf.get_int32(); - long channel = ser_buf.get_int32(); - double time = ser_buf.get_double(); - if (type == 'n') { - ser_buf.check_input_buffer(20); - float pitch = ser_buf.get_float(); - float loud = ser_buf.get_float(); - double dur = ser_buf.get_double(); - Alg_note *note = - create_note(time, channel, key, pitch, loud, dur); - note->set_selected(selected); - long param_num = ser_buf.get_int32(); - int j; - // this builds a list of parameters in the correct order - // (although order shouldn't matter) - Alg_parameters_ptr *list = ¬e->parameters; - for (j = 0; j < param_num; j++) { - *list = new Alg_parameters(NULL); - unserialize_parameter(&((*list)->parm)); - list = &((*list)->next); - } - append(note); - } else { - assert(type == 'u'); - Alg_update *update = create_update(time, channel, key); - update->set_selected(selected); - unserialize_parameter(&(update->parameter)); - append(update); - } - ser_buf.get_pad(); - } - assert(offset + bytes == ser_buf.get_posn()); -} - - -void Alg_track::unserialize_parameter(Alg_parameter_ptr parm_ptr) -{ - char *attr = ser_buf.get_string(); - parm_ptr->attr = symbol_table.insert_string(attr); - switch (parm_ptr->attr_type()) { - case 'r': - ser_buf.check_input_buffer(8); - parm_ptr->r = ser_buf.get_double(); - break; - case 's': - parm_ptr->s = heapify(ser_buf.get_string()); - break; - case 'i': - ser_buf.check_input_buffer(4); - parm_ptr->i = ser_buf.get_int32(); - break; - case 'l': - ser_buf.check_input_buffer(4); - parm_ptr->l = (bool) ser_buf.get_int32(); - break; - case 'a': - parm_ptr->a = symbol_table.insert_attribute(ser_buf.get_string()); - break; - } -} - - -void Alg_track::set_time_map(Alg_time_map *map) -{ - if (time_map) time_map->dereference(); - if (map == NULL) { - time_map = new Alg_time_map(); // new default map - time_map->reference(); - } else { - time_map = map; - time_map->reference(); - } -} - - -void Alg_track::convert_to_beats() -// modify all times and durations in notes to beats -{ - if (units_are_seconds) { - units_are_seconds = false; - long i; - - for (i = 0; i < length(); i++) { - Alg_event_ptr e = events[i]; - double beat = time_map->time_to_beat(e->time); - if (e->is_note()) { - Alg_note_ptr n = (Alg_note_ptr) e; - n->dur = time_map->time_to_beat(n->time + n->dur) - beat; - } - e->time = beat; - } - } -} - - -void Alg_track::convert_to_seconds() -// modify all times and durations in notes to seconds -{ - if (!units_are_seconds) { - last_note_off = time_map->beat_to_time(last_note_off); - units_are_seconds = true; - long i; - for (i = 0; i < length(); i++) { - Alg_event_ptr e = events[i]; - double time = time_map->beat_to_time(e->time); - if (e->is_note()) { - Alg_note_ptr n = (Alg_note_ptr) e; - n->dur = time_map->beat_to_time(n->time + n->dur) - time; - } - e->time = time; - } - } -} - - -void Alg_track::set_dur(double duration) -{ - // set beat_dur and real_dur - if (units_are_seconds) { - set_real_dur(duration); - set_beat_dur(time_map->time_to_beat(duration)); - } else { - set_beat_dur(duration); - set_real_dur(time_map->beat_to_time(duration)); - } -} - - -Alg_note *Alg_track::create_note(double time, int channel, int identifier, - float pitch, float loudness, double duration) -{ - Alg_note *note = new Alg_note(); - note->time = time; - note->chan = channel; - note->set_identifier(identifier); - note->pitch = pitch; - note->loud = loudness; - note->dur = duration; - return note; -} - - -Alg_update *Alg_track::create_update(double time, int channel, int identifier) -{ - Alg_update *update = new Alg_update(); - update->time = time; - update->chan = channel; - update->set_identifier(identifier); - return update; -} - - -Alg_track_ptr Alg_track::cut(double t, double len, bool all) -{ - // since we are translating notes in time, do not copy or use old timemap - Alg_track_ptr track = new Alg_track(); - track->units_are_seconds = units_are_seconds; - if (units_are_seconds) { - track->set_real_dur(len); - track->set_beat_dur(time_map->time_to_beat(t + len) - - time_map->time_to_beat(t)); - } else { - track->set_beat_dur(len); - track->set_real_dur(time_map->beat_to_time(t + len) - - time_map->beat_to_time(t)); - } - int i; - int new_len = 0; - int change = 0; - for (i = 0; i < length(); i++) { - Alg_event_ptr event = events[i]; - if (event->overlap(t, len, all)) { - event->time -= t; - track->append(event); - change = 1; - } else { // if we're not cutting this event, move it to - // eliminate the gaps in events left by cut events - events[new_len] = event; - // adjust times of events after t + len - if (event->time > t + len - ALG_EPS) { - event->time -= len; - change = 1; - } - new_len++; - } - } - // Alg_event_lists based on this track become invalid - sequence_number += change; - this->len = new_len; // adjust length since we removed events - return track; -} - - -Alg_track_ptr Alg_track::copy(double t, double len, bool all) -{ - // since we are translating notes in time, do not copy or use old timemap - Alg_track_ptr track = new Alg_track(); - track->units_are_seconds = units_are_seconds; - if (units_are_seconds) { - track->set_real_dur(len); - track->set_beat_dur(time_map->time_to_beat(t + len) - - time_map->time_to_beat(t)); - } else { - track->set_beat_dur(len); - track->set_real_dur(time_map->beat_to_time(t + len) - - time_map->beat_to_time(t)); - } - int i; - for (i = 0; i < length(); i++) { - Alg_event_ptr event = events[i]; - if (event->overlap(t, len, all)) { - Alg_event_ptr new_event = copy_event(event); - new_event->time -= t; - track->append(new_event); - } - } - return track; -} - - -void Alg_track::paste(double t, Alg_event_list *seq) -{ - assert(get_type() == 't'); - // seq can be an Alg_event_list, an Alg_track, or an Alg_seq - // if it is an Alg_event_list, units_are_seconds must match - bool prev_units_are_seconds = false; - if (seq->get_type() == 'e') { - assert(seq->get_owner()->get_units_are_seconds() == units_are_seconds); - } else { // make it match - Alg_track_ptr tr = (Alg_track_ptr) seq; - prev_units_are_seconds = tr->get_units_are_seconds(); - if (units_are_seconds) tr->convert_to_seconds(); - else tr->convert_to_beats(); - } - double dur = (units_are_seconds ? seq->get_real_dur() : - seq->get_beat_dur()); - - // Note: in the worst case, seq may contain notes - // that start almost anytime up to it's duration, - // so the simplest algorithm is simply a sequence - // of inserts. If this turns out to be too slow, - // we can do a merge sort in the case that seq - // is an Alg_track (if it's an Alg_event_list, we - // are not guaranteed that the events are in time - // order, but currently, only a true seq is allowed) - - int i; - for (i = 0; i < length(); i++) { - if (events[i]->time > t - ALG_EPS) { - events[i]->time += dur; - } - } - for (i = 0; i < seq->length(); i++) { - Alg_event *new_event = copy_event((*seq)[i]); - new_event->time += t; - insert(new_event); - } - // restore track units to what they were before - if (seq->get_type() != 'e') { - Alg_track_ptr tr = (Alg_track_ptr) seq; - if (prev_units_are_seconds) tr->convert_to_seconds(); - else tr->convert_to_beats(); - } - -} - - -void Alg_track::merge(double t, Alg_event_list_ptr seq) -{ - Alg_event_list_ref s = *seq; - for (int i = 0; i < s.length(); i++) { - Alg_event *new_event; - if (s[i]->is_note()) { - new_event = new Alg_note((Alg_note_ptr) s[i]); - } else { - new_event = new Alg_update((Alg_update_ptr) s[i]); - } - new_event->time += t; - insert(new_event); - } -} - - -void Alg_track::clear(double t, double len, bool all) -{ - int i; - int move_to = 0; - for (i = 0; i < length(); i++) { - Alg_event_ptr event = events[i]; - if (event->overlap(t, len, all)) { - delete events[i]; - } else { // if we're not clearing this event, move it to - // eliminate the gaps in events left by cleared events - events[move_to] = event; - // adjust times of events after t + len. This test is based - // on the one in Alg_event::overlap() for consistency. - if (event->time > t + len - ALG_EPS && event->time > t) - event->time -= len; - move_to++; - } - } - if (move_to != this->len) { // we cleared at least one note - sequence_number++; // Alg_event_lists based on this track become invalid - } - this->len = move_to; // adjust length since we removed events -} - - -void Alg_track::silence(double t, double len, bool all) -{ - int i; - int move_to = 0; - for (i = 0; i < length(); i++) { - Alg_event_ptr event = events[i]; - if (event->overlap(t, len, all)) { - delete events[i]; - } else { // if we're not clearing this event, move it to - // eliminate the gaps in events left by cleared events - events[move_to] = event; - move_to++; - } - } - if (move_to != this->len) { // we cleared at least one note - sequence_number++; // Alg_event_lists based on this track become invalid - } - this->len = move_to; // adjust length since we removed events -} - - -void Alg_track::insert_silence(double t, double len) -{ - int i; - for (i = 0; i < length(); i++) { - Alg_event_ptr event = events[i]; - if (event->time > t - ALG_EPS) event->time += len; - } -} - - -Alg_event_list *Alg_track::find(double t, double len, bool all, - long channel_mask, long event_type_mask) -{ - int i; - Alg_event_list *list = new Alg_event_list(this); - if (units_are_seconds) { // t and len are seconds - list->set_real_dur(len); - list->set_beat_dur(get_time_map()->time_to_beat(t + len) - - get_time_map()->time_to_beat(t)); - } else { // t and len are beats - list->set_real_dur(get_time_map()->beat_to_time(t + len) - - get_time_map()->beat_to_time(t)); - list->set_beat_dur(len); - } - for (i = 0; i < length(); i++) { - Alg_event_ptr event = events[i]; - if (event->overlap(t, len, all)) { - if ((channel_mask == 0 || - (event->chan < 32 && - (channel_mask & (1 << event->chan)))) && - ((event_type_mask == 0 || - (event_type_mask & (1 << event->get_type_code()))))) { - list->append(event); - } - } - } - return list; -} - - -void Alg_time_sigs::expand() -{ - maxlen = (maxlen + 5); // extra growth for small sizes - maxlen += (maxlen >> 2); // add 25% - Alg_time_sig_ptr new_time_sigs = new Alg_time_sig[maxlen]; - // now do copy - memcpy(new_time_sigs, time_sigs, len * sizeof(Alg_time_sig)); - if (time_sigs) delete[] time_sigs; - time_sigs = new_time_sigs; -} - - -void Alg_time_sigs::insert(double beat, double num, double den) -{ - // find insertion point: - for (int i = 0; i < len; i++) { - if (within(time_sigs[i].beat, beat, ALG_EPS)) { - // overwrite location i with new info - time_sigs[i].beat = beat; - time_sigs[i].num = num; - time_sigs[i].den = den; - return; - } else if (time_sigs[i].beat > beat) { - if ((i > 0 && // check if redundant with prev. time sig - time_sigs[i - 1].num == num && - time_sigs[i - 1].den == den && - within(fmod(beat - time_sigs[i - 1].beat, - 4 * time_sigs[i-1].num / time_sigs[i-1].den), - 0, ALG_EPS)) || - // check if redundant with implied initial 4/4 time sig: - (i == 0 && num == 4 && den == 4 && - within(fmod(beat, 4), 0, ALG_EPS))) { - return; // redundant inserts are ignored here - } - // make room for new event - if (maxlen <= len) expand(); - len++; - // insert new event at i - memmove(&time_sigs[i + 1], &time_sigs[i], - sizeof(Alg_time_sig) * (len - i)); - time_sigs[i].beat = beat; - time_sigs[i].num = num; - time_sigs[i].den = den; - return; - } - } - // if we fall out of loop, then this goes at end - if (maxlen <= len) expand(); - time_sigs[len].beat = beat; - time_sigs[len].num = num; - time_sigs[len].den = den; - len++; -} - - -void Alg_time_sigs::show() -{ - printf("Alg_time_sig: "); - for (int i = 0; i < len; i++) { - printf("(%g: %g/%g) ", time_sigs[i].beat, time_sigs[i].num, time_sigs[i].den); - } - printf("\n"); -} - - -int Alg_time_sigs::find_beat(double beat) -{ - // index where you would insert a new time signature at beat - int i = 0; - while (i < len && time_sigs[i].beat < beat - ALG_EPS) i++; - return i; -} - - -void Alg_time_sigs::cut(double start, double end) -{ - // remove time_sig's from start to start+len -- these must be - // in beats (not seconds) - // now rewrite time_sig[]: copy from i_in to i_out (more or less) - int i_in = 0; - int i_out = 0; - // first, figure out where to begin cut region - i_in = find_beat(start); - i_out = i_in; - // scan to end of cut region - while (i_in < len && time_sigs[i_in].beat < end) { - i_in = i_in + 1; - } - // change time_sig at start if necessary - // there's a time_sig that was skipped if i_in > i_out. - // if that's true and the next time change is at end, we're - // ok because it will be copied, but if the next time change - // is after end, then maybe we should insert a time change - // corresponding to what's in effect at end. We can skip this - // insert if it corresponds to whatever is in effect at start - if (i_in > i_out && i_in < len && - time_sigs[i_in].beat > end + ALG_EPS && - (i_out == 0 || time_sigs[i_out - 1].num != time_sigs[i_in - 1].num || - time_sigs[i_out - 1].den != time_sigs[i_in - 1].den)) { - time_sigs[i_out] = time_sigs[i_in - 1]; - time_sigs[i_out].beat = start; - } - // scan from end to len(time_sig) - while (i_in < length()) { - Alg_time_sig &ts = time_sigs[i_in]; - ts.beat = ts.beat - (end - start); - time_sigs[i_out] = ts; - i_in = i_in + 1; - i_out = i_out + 1; - } - len = i_out; -} - - -void Alg_time_sigs::trim(double start, double end) -{ - // remove time_sig's not in [start, start+end) - // units must be in beats (not seconds) - // copy from i_in to i_out as we scan time_sig array - int i_in = 0; - int i_out = 0; - // first, skip time signatures up to start - i_in = find_beat(start); - // put time_sig at start if necessary - // if 0 < i_in < len, then the time sig at i_in is either - // at start or after start. - // If after start, then insert time sig at i_in-1 at 0. - // Otherwise, we'll pick up time sig at i_in below. - // If 0 == i_in < len, then the time sig at i_in is either - // at start or after start. - // If after start, then time sig at 0 is 4/4, but that's the - // default, so do nothing. - // Otherwise, we'll pick up time sig at i_in below. - // If 0 < i_in == len, then insert time_sig at i_in-1 at start - // If 0 == i_in == len, then 4/4 default applies and we're done. - // - // So the conditions for inserting time_sig[in_i-1] at 0 are: - // (0 < i_in < len and time_sig[i] > start+ALG_EPS) OR - // (0 < i_in == len) - // We can rewrite this to - // (0 < i_in) && ((i_in < len && time_sig[i_in].beat > start + ALG_EPS) || - // (i_in == len))) - // - if (0 < i_in && ((i_in < len && time_sigs[i_in].beat > start + ALG_EPS) || - (i_in == len))) { - time_sigs[0] = time_sigs[i_in - 1]; - time_sigs[0].beat = 0.0; - i_out = 1; - } - // scan to end of cut region - while (i_in < len && time_sigs[i_in].beat < end - ALG_EPS) { - Alg_time_sig &ts = time_sigs[i_in]; - ts.beat = ts.beat - start; - time_sigs[i_out] = ts; - i_in++; - i_out++; - } - len = i_out; -} - - -void Alg_time_sigs::paste(double start, Alg_seq *seq) -{ - // printf("time_sig::insert before paste\n"); - // show(); - Alg_time_sigs &from = seq->time_sig; - // printf("time_sig::insert from\n"); - from.show(); - // insert time signatures from seq into this time_sigs at start - if (len == 0 && from.len == 0) { - return; // default applies - } - int i = find_beat(start); - // remember the time signature at the splice point - double num_after_splice = 4; - double den_after_splice = 4; // default - // three cases: - // 1) time sig at splice is at i-1 - // for this, we must have len>0 & i>0 - // two sub-cases: - // A) i < len && time_sig[i].beat > start - // B) i == len - // 2) time_sig at splice is at i - // for this, i < len && time_sig[i].beat ~= start - // 3) time_sig at splice is default 4/4 - if (len > 0 && i > 0 && - ((i < len && time_sigs[i].beat > start + ALG_EPS) || - (i == len))) { - num_after_splice = time_sigs[i-1].num; - den_after_splice = time_sigs[i-1].den; - } else if (i < len && time_sigs[i].beat <= start + ALG_EPS) { - num_after_splice = time_sigs[i].num; - den_after_splice = time_sigs[i].den; - } - // i is where insert will go, time_sig[i].beat > start - // begin by adding duration to time_sig's at i and above - // move time signatures forward by duration of seq - double dur = seq->get_beat_dur(); - while (i < len) { - time_sigs[i].beat += dur; - i++; - } - //printf("time_sig::insert after making space\n"); - //show(); - // now insert initial time_signature at start. This may create - // an extra measure if seq does not begin on a measure boundary - insert(start, 4, 4); // in case seq uses default starting signature - //printf("time_sig::insert after 4/4 at start\n"); - //show(); - // insert time signatures from seq offset by start - for (i = 0; i < from.length(); i++) { - insert(start + from[i].beat, from[i].num, from[i].den); - } - //printf("time_sig::insert after pasting in sigs\n"); - //show(); - // now insert time signature at end of splice - insert(start + dur, num_after_splice, den_after_splice); - //printf("time_sig::insert after sig at end of splice\n"); - //show(); -} - - -void Alg_time_sigs::insert_beats(double beat, double len) -{ - int i; - // find the time_sig entry in effect at t - for (i = 0; i < len; i++) { - if (time_sigs[i].beat < beat + ALG_EPS) { - break; - } - } - // now, increase beat times by len - for (; i < len; i++) { - time_sigs[i].beat += len; - } -} - - -Alg_tracks::~Alg_tracks() -{ - // Alg_events objects (track data) are not deleted, only the array - if (tracks) { - delete[] tracks; - } -} - - -void Alg_tracks::expand_to(int new_max) -{ - maxlen = new_max; - Alg_track_ptr *new_tracks = new Alg_track_ptr[maxlen]; - // now do copy - memcpy(new_tracks, tracks, len * sizeof(Alg_track_ptr)); - if (tracks) { - delete[] tracks; - } - tracks = new_tracks; -} - - -void Alg_tracks::expand() -{ - maxlen = (maxlen + 5); // extra growth for small sizes - maxlen += (maxlen >> 2); // add 25% - expand_to(maxlen); -} - - -void Alg_tracks::append(Alg_track_ptr track) -{ - if (maxlen <= len) { - expand(); - } - tracks[len] = track; - len++; -} - - -void Alg_tracks::add_track(int track_num, Alg_time_map_ptr time_map, - bool seconds) - // Create a new track at index track_num. - // If track already exists, this call does nothing. - // If highest previous track is not at track_num-1, then - // create tracks at len, len+1, ..., track_num. -{ - assert(track_num >= 0); - if (track_num == maxlen) { - // use eponential growth to insert tracks sequentially - expand(); - } else if (track_num > maxlen) { - // grow to exact size for random inserts - expand_to(track_num + 1); - } - if (track_num < len) return; // don't add if already there - while (len <= track_num) { - tracks[len] = new Alg_track(time_map, seconds); - //printf("allocated track at %d (%x, this %x) = %x\n", len, - // &(tracks[len]), this, tracks[len]); - len++; - } -} - - -void Alg_tracks::reset() -{ - // all track events are incorporated into the seq, - // so all we need to delete are the arrays of pointers - for (int i = 0; i < len; i++) { - delete tracks[i]; - } - if (tracks) delete [] tracks; - tracks = NULL; - len = 0; - maxlen = 0; // Modified by Ning Hu Nov.19 2002 -} - - -Alg_seq::Alg_seq(const char *filename, bool smf) -{ - basic_initialization(); - ifstream inf(filename, smf ? ios::binary | ios::in : ios::in); - if (inf.fail()) { - error = alg_error_open; - return; - } - if (smf) { - error = alg_smf_read(inf, this); - } else { - error = alg_read(inf, this); - } - inf.close(); -} - - -Alg_seq::Alg_seq(istream &file, bool smf) -{ - basic_initialization(); - if (smf) { - error = alg_smf_read(file, this); - } else { - error = alg_read(file, this); - } -} - -void Alg_seq::seq_from_track(Alg_track_ref tr) -{ - type = 's'; - // copy everything - set_beat_dur(tr.get_beat_dur()); - set_real_dur(tr.get_real_dur()); - // copy time_map - set_time_map(new Alg_time_map(tr.get_time_map())); - units_are_seconds = tr.get_units_are_seconds(); - - if (tr.get_type() == 's') { - Alg_seq_ref s = *(tr.to_alg_seq()); - channel_offset_per_track = s.channel_offset_per_track; - add_track(s.tracks() - 1); - // copy each track - for (int i = 0; i < tracks(); i++) { - Alg_track_ref from_track = *(s.track(i)); - Alg_track_ref to_track = *(track(i)); - to_track.set_beat_dur(from_track.get_beat_dur()); - to_track.set_real_dur(from_track.get_real_dur()); - if (from_track.get_units_are_seconds()) - to_track.convert_to_seconds(); - for (int j = 0; j < from_track.length(); j++) { - Alg_event_ptr event = copy_event(from_track[j]); - to_track.append(event); - } - } - } else if (tr.get_type() == 't') { - add_track(0); - channel_offset_per_track = 0; - Alg_track_ptr to_track = track(0); - to_track->set_beat_dur(tr.get_beat_dur()); - to_track->set_real_dur(tr.get_real_dur()); - for (int j = 0; j < tr.length(); j++) { - Alg_event_ptr event = copy_event(tr[j]); - to_track->append(event); - } - } else { - assert(false); // expected track or sequence - } -} - - -int Alg_seq::tracks() -{ - return track_list.length(); -} - - -Alg_track_ptr Alg_seq::track(int i) -{ - assert(0 <= i && i < track_list.length()); - return &(track_list[i]); -} - - -#pragma GCC diagnostic ignored "-Wreturn-type" // ok not to return a value here -Alg_event_ptr &Alg_seq::operator[](int i) -{ - int ntracks = track_list.length(); - int tr = 0; - while (tr < ntracks) { - Alg_track *a_track = track(tr); - if (a_track && i < a_track->length()) { - return (*a_track)[i]; - } else if (a_track) { - i -= a_track->length(); - } - tr++; - } - assert(false); // out of bounds -} - - -void Alg_seq::convert_to_beats() -{ - if (!units_are_seconds) return; - for (int i = 0; i < tracks(); i++) { - track(i)->convert_to_beats(); - } - // note that the Alg_seq inherits units_are_seconds from an - // empty track. Each track also has a (redundant) field called - // units are seconds. These should always be consistent. - units_are_seconds = false; -} - - -void Alg_seq::convert_to_seconds() -{ - if (units_are_seconds) return; - //printf("convert_to_seconds, tracks %d\n", tracks()); - //printf("last_tempo of seq: %g on map %x\n", - // get_time_map()->last_tempo, get_time_map()); - for (int i = 0; i < tracks(); i++) { - //printf("last_tempo of track %d: %g on %x\n", i, - // track(i)->get_time_map()->last_tempo, - // track(i)->get_time_map()); - track(i)->convert_to_seconds(); - } - // update our copy of last_note_off (which may or may not be valid) - last_note_off = time_map->beat_to_time(last_note_off); - // note that the Alg_seq inherits units_are_seconds from an - // empty track. Each track also has a (redundant) field called - // units are seconds. These should always be consistent. - units_are_seconds = true; -} - - -Alg_track_ptr Alg_seq::cut_from_track(int track_num, double start, - double dur, bool all) -{ - assert(track_num >= 0 && track_num < tracks()); - Alg_track_ptr tr = track(track_num); - return tr->cut(start, dur, all); -} - - -void Alg_seq::copy_time_sigs_to(Alg_seq *dest) -{ - // copy time signatures - for (int i = 0; i < time_sig.length(); i++) { - dest->time_sig.insert(time_sig[i].beat, time_sig[i].num, - time_sig[i].den); - } -} - - -void Alg_seq::set_time_map(Alg_time_map *map) -{ - Alg_track::set_time_map(map); - for (int i = 0; i < tracks(); i++) { - track(i)->set_time_map(map); - } -} - - -Alg_seq_ptr Alg_seq::cut(double start, double len, bool all) - // return sequence from start to start+len and modify this - // sequence by removing that time-span -{ - // fix parameters to fall within existing sequence - if (start > get_dur()) return NULL; // nothing to cut - if (start < 0) start = 0; // can't start before sequence starts - if (start + len > get_dur()) // can't cut after end: - len = get_dur() - start; - - Alg_seq_ptr result = new Alg_seq(); - Alg_time_map_ptr map = new Alg_time_map(get_time_map()); - result->set_time_map(map); - copy_time_sigs_to(result); - result->units_are_seconds = units_are_seconds; - result->track_list.reset(); - - for (int i = 0; i < tracks(); i++) { - Alg_track_ptr cut_track = cut_from_track(i, start, len, all); - result->track_list.append(cut_track); - // initially, result->last_note_off is zero. We want to know the - // maximum over all cut_tracks, so compute that here: - result->last_note_off = MAX(result->last_note_off, - cut_track->last_note_off); - // since we're moving to a new sequence, change the track's time_map - result->track_list[i].set_time_map(map); - } - - // put units in beats to match time_sig's. Note that we need - // two different end times. For result, we want the time of the - // last note off, but for cutting out the time signatures in this, - // we use len. - double ts_start = start; - double ts_end = start + len; - double ts_last_note_off = start + result->last_note_off; - if (units_are_seconds) { - ts_start = time_map->time_to_beat(ts_start); - ts_end = time_map->time_to_beat(ts_end); - ts_last_note_off = time_map->time_to_beat(ts_last_note_off); - } - // result is shifted from start to 0 and has length len, but - // time_sig and time_map are copies from this. Adjust time_sig, - // time_map, and duration fields in result. The time_sig and - // time_map data is retained out to last_note_off so that we have - // information for the entire duration of all the notes, even though - // this might extend beyond the duration of the track. (Warning: - // no info is retained for notes with negative times.) - result->time_sig.trim(ts_start, ts_last_note_off); - result->time_map->trim(start, start + result->last_note_off, - result->units_are_seconds); - // even though there might be notes sticking out beyond len, the - // track duration is len, not last_note_off. (Warning: if all is - // true, there may also be notes at negative offsets. These times - // cannot be mapped between beat and time representations, so there - // may be subtle bugs or unexpected behaviors in that case.) - result->set_dur(len); - - // we sliced out a portion of each track, so now we need to - // slice out the corresponding sections of time_sig and time_map - // as well as to adjust the duration. - time_sig.cut(ts_start, ts_end); - time_map->cut(start, len, units_are_seconds); - set_dur(get_dur() - len); - - return result; -} - - -void Alg_seq::insert_silence_in_track(int track_num, double t, double len) -{ - Alg_track_ptr tr = track(track_num); - tr->insert_silence(t, len); -} - - -void Alg_seq::insert_silence(double t, double len) -{ - for (int i = 0; i < tracks(); i++) { - insert_silence_in_track(i, t, len); - } - double t_beats = t; - double len_beats = len; - // insert into time_sig array; use time_sig_paste, - // which requires us to build a simple time_sig array - if (units_are_seconds) { - time_map->insert_time(t, len); - t_beats = time_map->time_to_beat(t); - len_beats = time_map->time_to_beat(t + len) - t_beats; - } else { - time_map->insert_beats(t_beats, len_beats); - } - if (time_sig.length() > 0) { - time_sig.insert_beats(t_beats, len_beats); - } -} - - -Alg_track_ptr Alg_seq::copy_track(int track_num, double t, double len, bool all) -{ - return track_list[track_num].copy(t, len, all); -} - - -Alg_seq *Alg_seq::copy(double start, double len, bool all) -{ - // fix parameters to fall within existing sequence - if (start > get_dur()) return NULL; // nothing to copy - if (start < 0) start = 0; // can't copy before sequence starts - if (start + len > get_dur()) // can't copy after end: - len = get_dur() - start; - - // return (new) sequence from start to start + len - Alg_seq_ptr result = new Alg_seq(); - Alg_time_map_ptr map = new Alg_time_map(get_time_map()); - result->set_time_map(map); - copy_time_sigs_to(result); - result->units_are_seconds = units_are_seconds; - result->track_list.reset(); - - for (int i = 0; i < tracks(); i++) { - Alg_track_ptr copy = copy_track(i, start, len, all); - result->track_list.append(copy); - result->last_note_off = MAX(result->last_note_off, - copy->last_note_off); - // since we're copying to a new seq, change the track's time_map - result->track_list[i].set_time_map(map); - } - - // put units in beats to match time_sig's. Note that we need - // two different end times. For result, we want the time of the - // last note off, but for cutting out the time signatures in this, - // we use len. - double ts_start = start; - double ts_end = start + len; - double ts_last_note_off = start + result->last_note_off; - if (units_are_seconds) { - ts_start = time_map->time_to_beat(ts_start); - ts_end = time_map->time_to_beat(ts_end); - ts_last_note_off = time_map->time_to_beat(ts_last_note_off); - } - - result->time_sig.trim(ts_start, ts_last_note_off); - result->time_map->trim(start, start + result->last_note_off, - units_are_seconds); - result->set_dur(len); - return result; -} - - -void Alg_seq::paste(double start, Alg_seq *seq) -{ - // insert seq at time; open up space for it - // to manipulate time map, we need units as beats - // save original form so we can convert back if necessary - bool units_should_be_seconds = units_are_seconds; - bool seq_units_should_be_seconds = seq->get_units_are_seconds(); - if (units_are_seconds) { - start = time_map->time_to_beat(start); - convert_to_beats(); - } - seq->convert_to_beats(); - - // do a paste on each track - int i; - for (i = 0; i < seq->tracks(); i++) { - if (i >= tracks()) { - add_track(i); - } - track(i)->paste(start, seq->track(i)); - } - // make sure all tracks were opened up for an insert, even if - // there is nothing to insert - while (i < tracks()) { - track(i)->insert_silence(start, seq->get_dur()); - i++; - } - // paste in tempo track - time_map->paste(start, seq); - // paste in time signatures - time_sig.paste(start, seq); - set_dur(get_beat_dur() + seq->get_dur()); - assert(!seq->units_are_seconds && !units_are_seconds); - if (units_should_be_seconds) { - convert_to_seconds(); - } - if (seq_units_should_be_seconds) { - seq->convert_to_seconds(); - } -} - - -void Alg_seq::merge(double t, Alg_event_list_ptr seq) -{ - // seq must be an Alg_seq: - assert(seq->get_type() == 's'); - Alg_seq_ptr s = (Alg_seq_ptr) seq; - for (int i = 0; i < s->tracks(); i++) { - if (tracks() <= i) add_track(i); - track(i)->merge(t, s->track(i)); - } -} - - -void Alg_seq::silence_track(int track_num, double start, double len, bool all) -{ - // remove events in [time, time + len) and close gap - Alg_track_ptr tr = track(track_num); - tr->silence(start, len, all); -} - - -void Alg_seq::silence(double t, double len, bool all) -{ - for (int i = 0; i < tracks(); i++) { - silence_track(i, t, len, all); - } -} - - -void Alg_seq::clear_track(int track_num, double start, double len, bool all) -{ - // remove events in [time, time + len) and close gap - Alg_track_ptr tr = track(track_num); - tr->clear(start, len, all); -} - - -void Alg_seq::clear(double start, double len, bool all) -{ - // Fix parameters to fall within existing sequence - if (start > get_dur()) return; // nothing to cut - if (start < 0) start = 0; // can't start before sequence starts - if (start + len > get_dur()) // can't cut after end: - len = get_dur() - start; - - for (int i = 0; i < tracks(); i++) - clear_track(i, start, len, all); - - // Put units in beats to match time_sig's. - double ts_start = start; - double ts_end = start + len; - if (units_are_seconds) { - ts_start = time_map->time_to_beat(ts_start); - ts_end = time_map->time_to_beat(ts_end); - } - - // we sliced out a portion of each track, so now we need to - // slice out the corresponding sections of time_sig and time_map - // as well as to adjust the duration. - time_sig.cut(ts_start, ts_end); - time_map->cut(start, len, units_are_seconds); - set_dur(get_dur() - len); -} - - -Alg_event_list_ptr Alg_seq::find_in_track(int track_num, double t, double len, - bool all, long channel_mask, - long event_type_mask) -{ - return track(track_num)->find(t, len, all, channel_mask, event_type_mask); -} - - -Alg_seq::~Alg_seq() -{ - int i, j; - // Tracks does not delete Alg_events elements - for (j = 0; j < track_list.length(); j++) { - Alg_track ¬es = track_list[j]; - // Alg_events does not delete notes - for (i = 0; i < notes.length(); i++) { - Alg_event_ptr event = notes[i]; - delete event; - } - } -} - - -long Alg_seq::seek_time(double time, int track_num) -// find index of first score event after time -{ - long i; - Alg_events ¬es = track_list[track_num]; - for (i = 0; i < notes.length(); i++) { - if (notes[i]->time > time) { - break; - } - } - return i; -} - - -bool Alg_seq::insert_beat(double time, double beat) -// insert a time,beat pair -// return true or false (false indicates an error, no update) -// it is an error to imply a negative tempo or to insert at -// a negative time -{ - if (time < 0 || beat < 0) return false; - if (time == 0.0 && beat > 0) - time = 0.000001; // avoid infinite tempo, offset time by 1us - if (time == 0.0 && beat == 0.0) - return true; // (0,0) is already in the map! - convert_to_beats(); // beats are invariant when changing tempo - time_map->insert_beat(time, beat); - return true; -} - - -bool Alg_seq::insert_tempo(double bpm, double beat) -{ - double bps = bpm / 60.0; // convert to beats per second - // change the tempo at the given beat until the next beat event - if (beat < 0) return false; - convert_to_beats(); // beats are invariant when changing tempo - double time = time_map->beat_to_time(beat); - long i = time_map->locate_time(time); - if (i >= time_map->beats.len || !within(time_map->beats[i].time, time, 0.000001)) { - insert_beat(time, beat); - } - // now i is index of beat where tempo will change - if (i == time_map->beats.len - 1) { - time_map->last_tempo = bps; - time_map->last_tempo_flag = true; - } else { // adjust all future beats - // compute the difference in beats - double diff = time_map->beats[i + 1].beat - time_map->beats[i].beat; - // convert beat difference to seconds at new tempo - diff = diff / bps; - // figure out old time difference: - double old_diff = time_map->beats[i + 1].time - time; - // compute difference too - diff = diff - old_diff; - // apply new_diff to score and beats - while (i < time_map->beats.len) { - time_map->beats[i].time = time_map->beats[i].time + diff; - i++; - } - } - return true; -} - - -void Alg_seq::add_event(Alg_event_ptr event, int track_num) - // add_event puts an event in a given track (track_num). - // The track must exist. The time and duration of the - // event are interpreted according to whether the Alg_seq - // is currently in beats or seconds (see convert_to_beats()) -{ - track_list[track_num].insert(event); -/* - if (event->is_note()) { - Alg_note_ptr n = (Alg_note_ptr) event; - trace("note %d at %g for %g\n", n->get_identifier(), n->time, n->dur); - } - */ -} - - -bool Alg_seq::set_tempo(double bpm, double start_beat, double end_beat) -// set tempo from start_beat to end_beat -{ - // this is an optimization, the test is repeated in Alg_time_seq::set_tempo() - if (start_beat >= end_beat) return false; - bool units_should_be_seconds = units_are_seconds; - convert_to_beats(); - bool result = time_map->set_tempo(bpm, start_beat, end_beat); - if (units_should_be_seconds) convert_to_seconds(); - return result; -} - - -void Alg_seq::set_time_sig(double beat, double num, double den) -{ - time_sig.insert(beat, num, den); -} - - -void Alg_seq::beat_to_measure(double beat, long *measure, double *m_beat, - double *num, double *den) -{ - // return [measure, beat, num, den] - double m = 0; // measure number - double bpm; - int tsx; - bpm = 4; - // assume 4/4 if no time signature - double prev_beat = 0; - double prev_num = 4; - double prev_den = 4; - - if (beat < 0) beat = 0; // negative measures treated as zero - - for (tsx = 0; tsx < time_sig.length(); tsx++) { - if (time_sig[tsx].beat <= beat) { - // round m up to an integer (but allow for a small - // numerical inaccuracy) - m = m + (long) (0.99 + (time_sig[tsx].beat - prev_beat) / bpm); - bpm = time_sig[tsx].num * 4 / time_sig[tsx].den; - prev_beat = time_sig[tsx].beat; - prev_num = time_sig[tsx].num; - prev_den = time_sig[tsx].den; - } else { - m = m + (beat - prev_beat) / bpm; - *measure = (long) m; - *m_beat = (m - *measure) * bpm; - *num = prev_num; - *den = prev_den; - return; - } - } - // if we didn't return yet, compute after last time signature - Alg_time_sig initial(0, 4, 4); - Alg_time_sig &prev = initial; - if (tsx > 0) { // use last time signature - prev = time_sig[time_sig.length() - 1]; - } - bpm = prev.num * 4 / prev.den; - m = m + (beat - prev.beat) / bpm; - *measure = (long) m; - *m_beat = (m - *measure) * bpm; - *num = prev.num; - *den = prev.den; -} - -/* -void Alg_seq::set_events(Alg_event_ptr *events, long len, long max) -{ - convert_to_seconds(); // because notes are in seconds - notes.set_events(events, len, max); -} -*/ - - -void Alg_seq::iteration_begin() -{ - // keep an array of indexes into tracks - current = new long[track_list.length()]; - int i; - for (i = 0; i < track_list.length(); i++) { - current[i] = 0; - } -} - - -Alg_event_ptr Alg_seq::iteration_next() - // return the next event in time from any track -{ - long cur; // a track index - // find lowest next time of any track: - double next = 1000000.0; - int i, track = 0; - for (i = 0; i < track_list.length(); i++) { - Alg_track &tr = track_list[i]; - cur = current[i]; - if (cur < tr.length() && tr[cur]->time < next) { - next = tr[cur]->time; - track = i; - } - } - if (next < 1000000.0) { - return track_list[track][current[track]++]; - } else { - return NULL; - } -} - - -void Alg_seq::iteration_end() -{ - delete[] current; -} - - -void Alg_seq::merge_tracks() -{ - long sum = 0; - long i; - for (i = 0; i < track_list.length(); i++) { - sum = sum + track(i)->length(); - } - // preallocate array for efficiency: - Alg_event_ptr *notes = new Alg_event_ptr[sum]; - iteration_begin(); - long notes_index = 0; - - Alg_event_ptr event; - while (( event = iteration_next() )) { - notes[notes_index++] = event; - } - track_list.reset(); // don't need them any more - add_track(0); - track(0)->set_events(notes, sum, sum); - iteration_end(); -} - - -// sr_letter_to_type = {"i": 'Integer', "r": 'Real', "s": 'String', -// "l": 'Logical', "a": 'Symbol'} - - diff --git a/plugins/MidiImport/portsmf/allegro.h b/plugins/MidiImport/portsmf/allegro.h deleted file mode 100644 index e83d4b46375..00000000000 --- a/plugins/MidiImport/portsmf/allegro.h +++ /dev/null @@ -1,945 +0,0 @@ -// Portsmf (also known as Allegro): -// music representation system, with -// extensible in-memory sequence structure -// upward compatible with MIDI -// implementations in C++ and Serpent -// external, text-based representation -// compatible with Aura -// -// SERIALBUFFER CLASS -// -// The Serial_buffer class is defined to support serialization and -// unserialization. A Serial_buffer is just a block of memory with -// a length and a read/write pointer. When writing, it can expand. -// -// SERIALIZATION -// -// The Alg_track class has static members: -// ser_buf -- a Serial_buffer -// When objects are serialized, they are first written to -// ser_buf, which is expanded whenever necessary. Then, when -// the length is known, new memory is allocated and the data -// is copied to a correctly-sized buffer and returned to caller. -// The "external" (callable from outside the library) -// serialization functions are: -// Alg_track::serialize() -// Alg_seq::serialize() -// The "internal" serialization functions to be called from within -// the library are: -// Alg_track::serialize_track(bool text) -// Alg_seq::serialize_seq(bool text) -// Alg_track::serialize_parameter( -// Alg_parameter *parm, bool text) -// These internal serialize functions append data to ser_buf The text -// flag says to write an ascii representation as opposed to binary. -// -// UNSERIALIZATION: -// -// The Alg_track class has a static member: -// unserialize(char *buffer, long len) -// that will unserialize anything -- an Alg_track or an Alg_seq. -// No other function should be called from outside the library. -// Internal unserialize functions are: -// Alg_seq::unserialize_seq() -// Alg_track::unserialize_track() -// Alg_track::unserialize_parameter(Alg_parameter_ptr parm_ptr) -// Just as serialization uses ser_buf for output, unserialization uses -// unser_buf for reading. unser_buf is another static member of Alg_track. - -#ifndef __ALLEGRO__ -#define __ALLEGRO__ -#include "debug.h" - -#include "lmmsconfig.h" - -#define ALG_EPS 0.000001 // epsilon -#define ALG_DEFAULT_BPM 100.0 // default tempo - -// are d1 and d2 within epsilon of each other? -bool within(double d1, double d2, double epsilon); - -char *heapify(const char *s); // put a string on the heap - - -// Alg_attribute is an atom in the symbol table -// with the special addition that the last -// character is prefixed to the string; thus, -// the attribute 'tempor' (a real) is stored -// as 'rtempor'. To get the string name, just -// use attribute+1. -typedef char *Alg_attribute; -#define alg_attr_name(a) ((a) + 1) -#define alg_attr_type(a) (*(a)) - -// Alg_atoms is a symbol table of Alg_attributes and other -// unique strings -class Alg_atoms { -public: - Alg_atoms() { - maxlen = len = 0; - atoms = NULL; - } - // insert/lookup an atttribute - Alg_attribute insert_attribute(Alg_attribute attr); - // insert/lookup attribute by name (without prefixed type) - Alg_attribute insert_string(const char *name); -private: - long maxlen; - long len; - char **atoms; - - // insert an Attriubute not in table after moving attr to heap - Alg_attribute insert_new(const char *name, char attr_type); - void expand(); // make more space -}; - -extern Alg_atoms symbol_table; - - -// an attribute/value pair. Since Alg_attribute names imply type, -// we try to keep attributes and values packaged together as -// Alg_parameter class -typedef class Alg_parameter { -public: - ~Alg_parameter(); - Alg_attribute attr; - union { - double r;// real - char *s; // string - long i; // integer - bool l; // logical - char *a; // symbol (atom) - }; // anonymous union - void copy(Alg_parameter *); // copy from another parameter - char attr_type() { return alg_attr_type(attr); } - char *attr_name() { return alg_attr_name(attr); } - void set_attr(Alg_attribute a) { attr = a; } - void show(); -} *Alg_parameter_ptr; - - -// a list of attribute/value pairs -typedef class Alg_parameters { -public: - class Alg_parameters *next; - Alg_parameter parm; - - Alg_parameters(Alg_parameters *list) { - next = list; - } - - //~Alg_parameters() { } - - // each of these routines takes address of pointer to the list - // insertion is performed without checking whether or not a - // parameter already exists with this attribute. See find() and - // remove_key() to assist in checking for and removing existing - // parameters. - // Note also that these insert_* methods convert name to an - // attribute. If you have already done the symbol table lookup/insert - // you can do these operations faster (in which case we should add - // another set of functions that take attributes as arguments.) - static void insert_real(Alg_parameters **list, char *name, double r); - // insert string will copy string to heap - static void insert_string(Alg_parameters **list, char *name, char *s); - static void insert_integer(Alg_parameters **list, char *name, long i); - static void insert_logical(Alg_parameters **list, char *name, bool l); - static void insert_atom(Alg_parameters **list, char *name, char *s); - static Alg_parameters *remove_key(Alg_parameters **list, const char *name); - // find an attribute/value pair - Alg_parameter_ptr find(Alg_attribute *attr); -} *Alg_parameters_ptr; - - -// these are type codes associated with certain attributes -// see Alg_track::find() where these are bit positions in event_type_mask -#define ALG_NOTE 0 // This is a note, not an update -#define ALG_GATE 1 // "gate" -#define ALG_BEND 2 // "bend" -#define ALG_CONTROL 3 // "control" -#define ALG_PROGRAM 4 // "program" -#define ALG_PRESSURE 5 // "pressure" -#define ALG_KEYSIG 6 // "keysig" -#define ALG_TIMESIG_NUM 7 // "timesig_num" -#define ALG_TIMESIG_DEN 8 // "timesig_den" -#define ALG_OTHER 9 // any other value - -// abstract superclass of Alg_note and Alg_update: -typedef class Alg_event { -protected: - bool selected; - char type; // 'e' event, 'n' note, 'u' update - long key; // note identifier - static const char* description; // static buffer for debugging (in Alg_event) -public: - double time; - long chan; - virtual void show() = 0; - // Note: there is no Alg_event() because Alg_event is an abstract class. - bool is_note() { return (type == 'n'); } // tell whether an Alg_event is a note - bool is_update() { return (type == 'u'); } // tell whether an Alg_event is a parameter update - char get_type() { return type; } // return 'n' for note, 'u' for update - int get_type_code(); // 1 = volume change, 2 = pitch bend, - // 3 = control change, 4 = program change, - // 5 = pressure change, 6 = key signature, - // 7 = time sig numerator, 8 = time sig denominator - bool get_selected() { return selected; } - void set_selected(bool b) { selected = b; } - // Note: notes are identified by a (channel, identifier) pair. - // For midi, the identifier is the key number (pitch). The identifier - // does not have to represent pitch; it's main purpose is to identify - // notes so that they can be named by subsequent update events. - long get_identifier() { return key; } // get MIDI key or note identifier of note or update - void set_identifier(long i) { key = i; } // set the identifier - // In all of these set_ methods, strings are owned by the caller and - // copied as necessary by the callee. For notes, an attribute/value - // pair is added to the parameters list. For updates, the single - // attribute/value parameter pair is overwritten. In all cases, the - // attribute (first argument) must agree in type with the second arg. - // The last letter of the attribute implies the type (see below). - void set_parameter(Alg_parameter_ptr new_parameter); - void set_string_value(char *attr, char *value); - void set_real_value(char *attr, double value); - void set_logical_value(char *attr, bool value); - void set_integer_value(char *attr, long value); - void set_atom_value(char *attr, char *atom); - - // Some note methods. These fail (via assert()) if this is not a note: - // - float get_pitch();// get pitch in steps -- use this even for MIDI - float get_loud(); // get loudness (MIDI velocity) - // times are in seconds or beats, depending upon the units_are_seconds - // flag in the containing sequence - double get_start_time(); // get start time in seconds or beats - double get_end_time(); // get end time in seconds or beats - double get_duration(); // get duration in seconds or beats - void set_pitch(float); - void set_loud(float); - void set_duration(double); - - // Notes have lists of attribute values. Attributes are converted - // to/from strings in this API to avoid explicit use of Alg_attribute - // types. Attribute names end with a type designation: 's', 'r', 'l', - // 'i', or 'a'. - // - bool has_attribute(char *attr); // test if note has attribute/value pair - char get_attribute_type(char *attr); // get the associated type: - // 's' = string, - // 'r' = real (double), 'l' = logical (bool), 'i' = integer (long), - // 'a' = atom (char *), a unique string stored in Alg_seq - char *get_string_value(char *attr, char *value = NULL); // get the string value - double get_real_value(char *attr, double value = 0.0); // get the real value - bool get_logical_value(char *attr, bool value = false); // get the logical value - long get_integer_value(char *attr, long value = 0); // get the integer value - char *get_atom_value(char *attr, char *value = NULL); // get the atom value - void delete_attribute(char *attr); // delete an attribute/value pair - // (ignore if no matching attribute/value pair exists) - - // Some attribute/value methods. These fail if this is not an update. - // Attributes are converted to/from strings to avoid explicit use - // of Alg_attribute types. - // - const char *get_attribute(); // get the update's attribute (string) - char get_update_type(); // get the update's type: 's' = string, - // 'r' = real (double), 'l' = logical (bool), 'i' = integer (long), - // 'a' = atom (char *), a unique string stored in Alg_seq - char *get_string_value(); // get the update's string value - // Notes: Caller does not own the return value. Do not modify. - // Do not use after underlying Alg_seq is modified. - double get_real_value(); // get the update's real value - bool get_logical_value(); // get the update's logical value - long get_integer_value(); // get the update's integer value - char *get_atom_value(); // get the update's atom value - // Notes: Caller does not own the return value. Do not modify. - // The return value's lifetime is forever. - - // Auxiliary function to aid in editing tracks - // Returns true if the event overlaps the given region - bool overlap(double t, double len, bool all); - - const char *GetDescription(); // computes a text description of this event - // the result is in a static buffer, not thread-safe, just for debugging. - Alg_event() { selected = false; } - virtual ~Alg_event() {} -} *Alg_event_ptr; - - -typedef class Alg_note : public Alg_event { -public: - virtual ~Alg_note(); - Alg_note(Alg_note *); // copy constructor - float pitch; // pitch in semitones (69 = A440) - float loud; // dynamic corresponding to MIDI velocity - double dur; // duration in seconds (normally to release point) - Alg_parameters_ptr parameters; // attribute/value pair list - Alg_note() { type = 'n'; parameters = NULL; } - void show(); -} *Alg_note_ptr; - - -typedef class Alg_update : public Alg_event { -public: - virtual ~Alg_update() {}; - Alg_update(Alg_update *); // copy constructor - Alg_parameter parameter; // an update contains one attr/value pair - - - Alg_update() { type = 'u'; } - void show(); -} *Alg_update_ptr; - - -// a sequence of Alg_event objects -typedef class Alg_events { -private: - long maxlen; - void expand(); -protected: - long len; - Alg_event_ptr *events; // events is array of pointers -public: - // sometimes, it is nice to have the time of the last note-off. - // In the current implementation, - // this field is set by append to indicate the time of the - // last note-off in the current unit, so it should be correct after - // creating a new track and adding notes to it. It is *not* - // updated after uninsert(), so use it with care. - double last_note_off; - virtual int length() { return len; } - Alg_event_ptr &operator[](int i) { - assert(i >= 0 && i < len); - return events[i]; - } - Alg_events() { - maxlen = len = 0; - events = NULL; - last_note_off = 0; - } - // destructor deletes the events array, but not the - // events themselves - ~Alg_events(); - void set_events(Alg_event_ptr *e, long l, long m) { - if (events) delete [] events; - events = e; len = l; maxlen = m; } - // for use by Alg_track and Alg_seq - void insert(Alg_event_ptr event); - void append(Alg_event_ptr event); - Alg_event_ptr uninsert(long index); -} *Alg_events_ptr; - -class Alg_track; - -typedef class Alg_event_list : public Alg_events { -protected: - char type; // 'e' Alg_event_list, 't' Alg_track, 's' Alg_seq - static const char *last_error_message; - Alg_track *events_owner; // if this is an Alg_event_list, - // the events are owned by an Alg_track or an Alg_seq - static int sequences; // to keep track of sequence numbers - int sequence_number; // this sequence number is incremented - // whenever an edit is performed on an Alg_track or Alg_seq. - // When an Alg_event_list is created to contain pointers to - // a subset of an Alg_track or Alg_seq (the events_owner), - // the Alg_event_list gets a copy of the events_owner's - // sequence_number. If the events_owner is edited, the pointers - // in this Alg_event_list will become invalid. This is detected - // (for debugging) as differing sequence_numbers. - - // every event list, track, and seq has a duration. - // Usually the duration is set when the list is constructed, e.g. - // when you extract from 10 to 15 seconds, the duration is 5 secs. - // The duration does not tell you when is the last note-off. - // duration is recorded in both beats and seconds: - double beat_dur; - double real_dur; -public: - // the client should not create one of these, but these are - // returned from various track and seq operations. An - // Alg_event_list "knows" the Alg_track or Alg_seq that "owns" - // the events. All events in an Alg_event_list must belong - // to the same Alg_track or Alg_seq structure. - // When applied to an Alg_seq, events are enumerated track - // by track with increasing indices. This operation is not - // particularly fast on an Alg_seq. - virtual Alg_event_ptr &operator[](int i); - Alg_event_list() { sequence_number = 0; - beat_dur = 0.0; real_dur = 0.0; events_owner = NULL; type = 'e'; } - Alg_event_list(Alg_track *owner); - - char get_type() { return type; } - Alg_track *get_owner() { return events_owner; } - - // The destructor does not free events because they are owned - // by a track or seq structure. - virtual ~Alg_event_list(); - - // Returns the duration of the sequence in beats or seconds - double get_beat_dur() { return beat_dur; } - void set_beat_dur(double d) { beat_dur = d; } - double get_real_dur() { return real_dur; } - void set_real_dur(double d) { real_dur = d; } - - // Events are stored in time order, so when you change the time of - // an event, you must adjust the position. When you call set_start_time - // on an Alg_event_list, the Alg_event_list is not modified, but the - // Alg_track that "owns" the event is modified. If the owner is an - // Alg_seq, this may require searching the seq for the track containing - // the event. This will mean a logN search of every track in the seq - // (but if this turns out to be a problem, we can store each event's - // track owner in the Alg_event_list.) - virtual void set_start_time(Alg_event *event, double); - // get text description of run-time errors detected, clear error - const char *get_last_error_message() { return last_error_message; } - // Implementation hint: keep a sequence number on each Alg_track that is - // incremented anytime there is a structural change. (This behavior is - // inherited by Alg_seq as well.) Copy the sequence number to any - // Alg_event_list object when it is created. Whenever you access an - // Alg_event_list, using operator[], assert that the Alg_event_list sequence - // number matches the Alg_seq sequence number. This will guarantee that you - // do not try to retain pointers to events beyond the point where the events - // may no longer exist. -} *Alg_event_list_ptr, &Alg_event_list_ref; - - -// Alg_beat is used to contruct a tempo map -typedef class Alg_beat { -public: - Alg_beat(double t, double b) { - time = t; beat = b; } - Alg_beat() {}; - double time; - double beat; -} *Alg_beat_ptr; - - -// Alg_beats is a list of Alg_beat objects used in Alg_seq -typedef class Alg_beats { -private: - long maxlen; - void expand(); -public: - long len; - Alg_beat_ptr beats; - Alg_beat &operator[](int i) { - assert(i >= 0 && i < len); - return beats[i]; - } - Alg_beats() { - maxlen = len = 0; - beats = NULL; - expand(); - beats[0].time = 0; - beats[0].beat = 0; - len = 1; - } - ~Alg_beats() { - if (beats) delete[] beats; - } - void insert(long i, Alg_beat_ptr beat); -} *Alg_beats_ptr; - - -typedef class Alg_time_map { -private: - int refcount; -public: - Alg_beats beats; // array of Alg_beat - double last_tempo; - bool last_tempo_flag; - Alg_time_map() { - last_tempo = ALG_DEFAULT_BPM / 60.0; // note: this value ignored until - // last_tempo_flag is set; nevertheless, the default - // tempo is 100. - last_tempo_flag = true; - refcount = 0; - } - Alg_time_map(Alg_time_map *map); // copy constructor - long length() { return beats.len; } - void show(); - long locate_time(double time); - long locate_beat(double beat); - double beat_to_time(double beat); - double time_to_beat(double time); - // Time map manipulations: it is prefered to call the corresponding - // methods in Alg_seq. If you manipulate an Alg_time_map directly, - // you should take care to convert all tracks that use the time map - // to beats or seconds as appropriate: Normally if you insert a beat - // you want tracks to be in time units and if you insert a tempo change - // you want tracks to be in beat units. - void insert_beat(double time, double beat); // add a point to the map - bool insert_tempo(double tempo, double beat); // insert a tempo change - // set the tempo over a region - bool set_tempo(double tempo, double start_beat, double end_beat); - void cut(double start, double len, bool units_are_seconds); - void trim(double start, double end, bool units_are_seconds); - void paste(double start, Alg_track *tr); - // insert a span of time. If start is at a tempo change, then - // the span of time runs at the changed tempo - void insert_time(double start, double len); - // insert a span of beats. If start is at a tempo change, the - // tempo change takes effect before the inserted beats - void insert_beats(double start, double len); - void dereference() { - if (--refcount <= 0) delete this; - } - void reference() { - refcount++; - } -} *Alg_time_map_ptr; - - -typedef class Serial_buffer { -private: - char *buffer; - char *ptr; - long len; -public: - Serial_buffer() { - buffer = NULL; - ptr = NULL; - len = 0; - } - void init_for_write() { ptr = buffer; } - long get_posn() { return (long) (ptr - buffer); } - long get_len() { return len; } - // store_long writes a long at a given offset - void store_long(long offset, long value) { - assert(offset <= get_posn() - 4); - long *loc = (long *) (buffer + offset); - *loc = value; - } - void check_buffer(long needed); - void set_string(char *s) { - char *fence = buffer + len; - assert(ptr < fence); - while ((*ptr++ = *s++)) assert(ptr < fence); - // assert((char *)(((long) (ptr + 7)) & ~7) <= fence); - pad(); } - void set_int32(long v) { *((long *) ptr) = v; ptr += 4; } - void set_double(double v) { *((double *) ptr) = v; ptr += 8; } - void set_float(float v) { *((float *) ptr) = v; ptr += 4; } - void set_char(char v) { *ptr++ = v; } -#ifdef LMMS_BUILD_WIN64 - void pad() { while (((long long) ptr) & 7) set_char(0); } -#else - void pad() { while (((long) ptr) & 7) set_char(0); } -#endif - void *to_heap(long *len) { - *len = get_posn(); - char *newbuf = new char[*len]; - memcpy(newbuf, buffer, *len); - return newbuf; - } - void init_for_read(void *buf, long n) { - buffer = (char *) buf; - ptr = (char *) buf; - len = n; - } - char get_char() { return *ptr++; } - long get_int32() { long i = *((long *) ptr); ptr += 4; return i; } - float get_float() { float f = *((float *) ptr); ptr += 4; return f; } - double get_double() { double d = *((double *) ptr); ptr += sizeof(double); - return d; } - char *get_string() { char *s = ptr; char *fence = buffer + len; - assert(ptr < fence); - while (*ptr++) assert(ptr < fence); - get_pad(); - return s; } -#ifdef LMMS_BUILD_WIN64 - void get_pad() { while (((long long) ptr) & 7) ptr++; } -#else - void get_pad() { while (((long) ptr) & 7) ptr++; } -#endif - void check_input_buffer(long needed) { - assert(get_posn() + needed <= len); } -} *Serial_buffer_ptr; - -typedef class Alg_seq *Alg_seq_ptr; - -typedef class Alg_track : public Alg_event_list { -protected: - Alg_time_map *time_map; - bool units_are_seconds; - char *get_string(char **p, long *b); - long get_int32(char **p, long *b); - double get_double(char **p, long *b); - float get_float(char **p, long *b); - static Serial_buffer ser_buf; - void serialize_parameter(Alg_parameter *parm); - // *buffer_ptr points to binary data, bytes_ptr points to how many - // bytes have been used so far, len is length of binary data - void unserialize_parameter(Alg_parameter_ptr parm_ptr); -public: - void serialize_track(); - void unserialize_track(); - virtual Alg_event_ptr &operator[](int i) { - assert(i >= 0 && i < len); - return events[i]; - } - Alg_track() { units_are_seconds = false; time_map = NULL; - set_time_map(NULL); type = 't'; } - // initialize empty track with a time map - Alg_track(Alg_time_map *map, bool seconds); - - Alg_event_ptr copy_event(Alg_event_ptr event); // make a complete copy - - Alg_track(Alg_track &track); // copy constructor, does not copy time_map - // copy constructor: event_list is copied, map is installed and referenced - Alg_track(Alg_event_list_ref event_list, Alg_time_map_ptr map, - bool units_are_seconds); - virtual ~Alg_track() { set_time_map(NULL); } - - // Returns a buffer containing a serialization of the - // file. It will be an ASCII representation unless text is true. - // *buffer gets a newly allocated buffer pointer. The caller must free it. - // *len gets the length of the serialized track - virtual void serialize(void **buffer, long *bytes); - - // Try to read from a memory buffer. Automatically guess - // whether it's MIDI or text. - static Alg_track *unserialize(void *buffer, long len); - - // If the track is really an Alg_seq and you need to access an - // Alg_seq method, coerce to an Alg_seq with this function: - Alg_seq_ptr to_alg_seq() { - return (get_type() == 's' ? (Alg_seq_ptr) this : NULL); } - - // Are we using beats or seconds? - bool get_units_are_seconds() { return units_are_seconds; } - // Change units - virtual void convert_to_beats(); - virtual void convert_to_seconds(); - void set_dur(double dur); - double get_dur() { return (units_are_seconds ? real_dur : beat_dur); } - - // Every Alg_track may have an associated time_map. If no map is - // specified, or if you set_time_map(NULL), then the behavior - // should be as if there is a constant tempo of 100 beats/minute - // (this constant is determined by ALG_DEFAULT_BPM). - // Recommendation: create a static global tempo map object. When - // any operation that needs a tempo map gets NULL, use the global - // tempo map. (Exception: any operation that would modify the - // tempo map should raise an error -- you don't want to change the - // default tempo map.) - virtual void set_time_map(Alg_time_map *map); - Alg_time_map *get_time_map() { return time_map; } - - // Methods to create events. The returned event is owned by the caller. - // Use delete to get rid of it unless you call add() -- see below. - // - Alg_note *create_note(double time, int channel, int identifier, - float pitch, float loudness, double duration); - // Note: after create_update(), caller should use set_*_value() to - // initialize the attribute/value pair: - Alg_update *create_update(double time, int channel, int identifier); - // Adds a new event - it is automatically inserted into the - // correct order in the sequence based on its timestamp. - // The ownership passes from the caller to this Alg_seq. The - // event is not copied. - virtual void add(Alg_event *event) { insert(event); } - - // - // Editing regions - // - - // Deletes the notes that start within the given region - // and returns them in a new sequence. The start times - // of the notes in the returned sequence are shifted - // by -t. The notes after the region get shifted over - // to fill the gap. In an Alg_seq, the tempo track is edited - // in a similar way - // and the cut tempo information is retained in the new seq. - // ONLY NOTES THAT START WITHIN THE REGION ARE CUT unless - // "all" is true in which case all notes that intersect - // the region are copied. CUT NOTES - // MAY EXTEND BEYOND THE DURATION OF THE RESULTING SEQ. - // The return type is the same as this (may be Alg_seq). - // All times including len are interpreted according to - // units_are_seconds in the track. - virtual Alg_track *cut(double t, double len, bool all); - - // Like cut() but doesn't remove the notes from the original - // sequence. The Alg_events are copied, not shared. ONLY EVENTS - // THAT START WITHIN THE REGION ARE COPIED unless "all" is true - // in which case all notes that intersect the region are - // copied. COPIED NOTES MAY - // EXTEND BEYOND THE DURATION OF THE RESULTING SEQ. - // The return type is the same as this (may be Alg_seq). - virtual Alg_track *copy(double t, double len, bool all); - - // Inserts a sequence in the middle, shifting some notes - // over by the duration of the seq, which is first converted - // to the same units (seconds or beats) as this. (This makes - // a differece because the pasted data may change the tempo, - // and notes that overlap the borders will then experience - // a tempo change.) - // THE SEQ PARAMETER IS NOT MODIFIED, AND Alg_event's ARE - // COPIED, NOT SHARED. - // The type of seq must be Alg_seq if seq is an Alg_seq, or - // Alg_track if seq is an Alg_track or an Alg_event_list. - virtual void paste(double t, Alg_event_list *seq); // Shifts notes - - // Merges two sequences with a certain offset. The offset is - // interpreted as either beats or seconds according to the - // current units of this, and seq is converted to the same - // units as this. Except for a possible conversion to beats - // or seconds, the tempo track of seq (if any) is ignored. - // (There is no way to merge tempo tracks.) - // THE SEQ PARAMETER IS NOT MODIFIED, AND Alg_event's ARE - // COPIED, NOT SHARED. - // The type of seq must be Alg_seq if seq is an Alg_seq, or - // Alg_track if seq is an Alg_track or an Alg_event_list. - virtual void merge(double t, Alg_event_list_ptr seq); - - // Deletes and shifts notes to fill the gap. The tempo track - // is also modified accordingly. ONLY EVENTS THAT START WITHIN - // THE REGION ARE DELETED unless "all" is true, in which case - // all notes that intersect the region are cleared. - // NOTES THAT EXTEND FROM BEFORE THE - // REGION INTO THE REGION RETAIN THEIR DURATION IN EITHER - // BEATS OR SECONDS ACCORDING TO THE CURRENT UNITS OF this. - virtual void clear(double t, double len, bool all); - - // Deletes notes but doesn't shift. If the "all" argument - // is true, deletes all notes that intersect the range at all, - // not just those that start within it. The tempo track is - // not affected. - virtual void silence(double t, double len, bool all); - - // Simply shifts notes past time t over by len, which is given - // in either beats or seconds according to the units of this. - // The resulting interveal (t, t+len) may in fact contain notes - // that begin before t. The durations of notes are not changed. - // If this is an Alg_seq, the tempo track is expanded at t also. - virtual void insert_silence(double t, double len); - - // - // Accessing for screen display - // - - // A useful generic function to retrieve only certain - // types of events. The masks should be bit-masks defined - // somewhere else. Part of the mask allows us to search for - // selected events. If this is an Alg_seq, search all tracks - // (otherwise, call track[i].find()) - // If channel_mask == 0, accept ALL channels - virtual Alg_event_list *find(double t, double len, bool all, - long channel_mask, long event_type_mask); - - // - // MIDI playback - // - // See Alg_iterator -} *Alg_track_ptr, &Alg_track_ref; - - -// Alg_time_sig represents a single time signature; -// although not recommended, time_signatures may have arbitrary -// floating point values, e.g. 4.5 beats per measure -typedef class Alg_time_sig { -public: - double beat; // when does this take effect? - double num; // what is the "numerator" (top number?) - double den; // what is the "denominator" (bottom number?) - Alg_time_sig(double b, double n, double d) { - beat = b; num = n; den = d; - } - Alg_time_sig() { - beat = 0; num = 0; den = 0; - } - void beat_to_measure(double beat, double *measure, double *m_beat, - double *num, double *den); - -} *Alg_time_sig_ptr; - - -// Alg_time_sigs is a dynamic array of time signatures -// -// The default (empty) time_sigs has 4/4 time at beat 0. -// Each time_sig object in time_sigs represents the beginning -// of a measure. If there is a beat missing, e.g. in the first -// measure, you can represent this by inserting another -// time_sig at the next measure beginning. Each time_sig implies -// an infinite sequence of full measures until the next time_sig. -// If you insert a time_sig and one already exist near the same -// beat, the old one is replaced, thus re-barring every measure -// until the next time_sig. -class Alg_time_sigs { -private: - long maxlen; - void expand(); // make more space - long len; - Alg_time_sig_ptr time_sigs; -public: - Alg_time_sigs() { - maxlen = len = 0; - time_sigs = NULL; - } - Alg_time_sig &operator[](int i) { // fetch a time signature - assert(i >= 0 && i < len); - return time_sigs[i]; - } - ~Alg_time_sigs() { - if (time_sigs) delete[] time_sigs; - } - void show(); - long length() { return len; } - int find_beat(double beat); - void insert(double beat, double num, double den); - void cut(double start, double end); // remove from start to end - void trim(double start, double end); // retain just start to end - void paste(double start, Alg_seq *seq); - void insert_beats(double beat, double len); // insert len beats at beat -}; - - -// a sequence of Alg_events objects -typedef class Alg_tracks { -private: - long maxlen; - void expand(); - void expand_to(int new_max); - long len; -public: - Alg_track_ptr *tracks; // tracks is array of pointers - Alg_track &operator[](int i) { - assert(i >= 0 && i < len); - return *tracks[i]; - } - long length() { return len; } - Alg_tracks() { - maxlen = len = 0; - tracks = NULL; - } - ~Alg_tracks(); - // Append a track to tracks. This Alg_tracks becomes the owner of track. - void append(Alg_track_ptr track); - void add_track(int track_num, Alg_time_map_ptr time_map, bool seconds); - void reset(); -} *Alg_tracks_ptr; - - -typedef enum { - alg_no_error = 0, // no error reading Allegro or MIDI file - alg_error_open = -800, // could not open Allegro or MIDI file - alg_error_syntax // something found in the file that could not be parsed; - // generally you should ignore syntax errors or look at the printed error messages - // because there are some things in standard midi files that we do not handle; - // (maybe we should only set alg_error_syntax when there is a real problem with - // the file as opposed to when there is some warning message for the user) -} Alg_error; - - -// An Alg_seq is an array of Alg_events, each a sequence of Alg_event, -// with a tempo map and a sequence of time signatures -// -typedef class Alg_seq : public Alg_track { -protected: - long *current; // array of indexes used by iteration methods - void serialize_seq(); - Alg_error error; // error code set by file readers - // an internal function used for writing Allegro track names - Alg_event_ptr write_track_name(std::ostream &file, int n, - Alg_events &events); -public: - int channel_offset_per_track; // used to encode track_num into channel - Alg_tracks track_list; // array of Alg_events - Alg_time_sigs time_sig; - int beat_x; - void basic_initialization() { - error = alg_no_error; - units_are_seconds = true; type = 's'; - channel_offset_per_track = 0; - add_track(0); // default is one empty track - } - Alg_seq() { - basic_initialization(); - } - // copy constructor -- if track is an Alg_seq, make a copy; if - // track is just an Alg_track, the track becomes track 0 - Alg_seq(Alg_track_ref track) { seq_from_track(track); } - Alg_seq(Alg_track_ptr track) { seq_from_track(*track); } - void seq_from_track(Alg_track_ref tr); - Alg_seq(std::istream &file, bool smf); // create from file - Alg_seq(const char *filename, bool smf); // create from filename - ~Alg_seq(); - int get_read_error() { return error; } - void serialize(void **buffer, long *bytes); - void copy_time_sigs_to(Alg_seq *dest); // a utility function - void set_time_map(Alg_time_map *map); - - // encode sequence structure into contiguous, moveable memory block - // address of newly allocated memory is assigned to *buffer, which must - // be freed by caller; the length of data is assigned to *len - void unserialize_seq(); - - // write an ascii representation to file - void write(std::ostream &file, bool in_secs); - // returns true on success - bool write(const char *filename); - void smf_write(std::ofstream &file); - bool smf_write(const char *filename); - - // Returns the number of tracks - int tracks(); - - // create a track - void add_track(int track_num) { - track_list.add_track(track_num, get_time_map(), units_are_seconds); - } - - // Return a particular track. This Alg_seq owns the track, so the - // caller must not delete the result. - Alg_track_ptr track(int); - - virtual Alg_event_ptr &operator[](int i); - - virtual void convert_to_seconds(); - virtual void convert_to_beats(); - - Alg_track_ptr cut_from_track(int track_num, double start, double dur, - bool all); - Alg_seq *cut(double t, double len, bool all); - void insert_silence_in_track(int track_num, double t, double len); - void insert_silence(double t, double len); - Alg_track_ptr copy_track(int track_num, double t, double len, bool all); - Alg_seq *copy(double start, double len, bool all); - void paste(double start, Alg_seq *seq); - virtual void clear(double t, double len, bool all); - virtual void merge(double t, Alg_event_list_ptr seq); - virtual void silence(double t, double len, bool all); - void clear_track(int track_num, double start, double len, bool all); - void silence_track(int track_num, double start, double len, bool all); - Alg_event_list_ptr find_in_track(int track_num, double t, double len, - bool all, long channel_mask, - long event_type_mask); - - // find index of first score event after time - long seek_time(double time, int track_num); - bool insert_beat(double time, double beat); - // warning: insert_tempo may change representation from seconds to beats - bool insert_tempo(double bpm, double beat); - - // add_event takes a pointer to an event on the heap. The event is not - // copied, and this Alg_seq becomes the owner and freer of the event. - void add_event(Alg_event_ptr event, int track_num); - void add(Alg_event_ptr event) { assert(false); } // call add_event instead - // warning: set_tempo may change representation from seconds to beats - bool set_tempo(double bpm, double start_beat, double end_beat); - void set_time_sig(double beat, double num, double den); - void beat_to_measure(double beat, long *measure, double *m_beat, - double *num, double *den); - // void set_events(Alg_event_ptr *events, long len, long max); - void merge_tracks(); // move all track data into one track - void iteration_begin(); // prepare to enumerate events in order - Alg_event_ptr iteration_next(); // return next event (or NULL) - void iteration_end(); // clean up after enumerating events -} *Alg_seq_ptr, &Alg_seq_ref; - - -// see Alg_seq::Alg_seq() constructors that read from files -// the following are for internal library implementation and are -// moved to *_internal.h header files. -//Alg_seq_ptr alg_read(std::istream &file, Alg_seq_ptr new_seq); -//Alg_seq_ptr alg_smf_read(std::istream &file, Alg_seq_ptr new_seq); -#endif diff --git a/plugins/MidiImport/portsmf/allegrord.cpp b/plugins/MidiImport/portsmf/allegrord.cpp deleted file mode 100644 index 7a1f5beed6f..00000000000 --- a/plugins/MidiImport/portsmf/allegrord.cpp +++ /dev/null @@ -1,753 +0,0 @@ -#include "debug.h" -#include "stdlib.h" -#include "string.h" -#include "ctype.h" -#include "trace.h" -#include -#include -#include -#include "strparse.h" -#include "allegro.h" -#include "algrd_internal.h" - -using namespace std; - -#define streql(s1, s2) (strcmp(s1, s2) == 0) -#define field_max 80 - -class Alg_reader { -public: - istream *file; - string input_line; - int line_no; - String_parse line_parser; - bool line_parser_flag; - string field; - bool error_flag; - Alg_seq_ptr seq; - double tsnum; - double tsden; - - Alg_reader(istream *a_file, Alg_seq_ptr new_seq); - void readline(); - Alg_parameters_ptr process_attributes(Alg_parameters_ptr attributes, - double time); - bool parse(); - long parse_chan(string &field); - long parse_int(string &field); - int find_real_in(string &field, int n); - double parse_real(string &field); - void parse_error(string &field, long offset, const char *message); - double parse_dur(string &field, double base); - double parse_after_dur(double dur, string &field, int n, double base); - double parse_loud(string &field); - long parse_key(string &field); - double parse_pitch(string &field); - long parse_after_key(int key, string &field, int n); - long find_int_in(string &field, int n); - bool parse_attribute(string &field, Alg_parameter_ptr parm); - bool parse_val(Alg_parameter_ptr param, string &s, int i); - bool check_type(char type_char, Alg_parameter_ptr param); -}; - - -double Alg_reader::parse_pitch(string &field) -{ - if (isdigit(field[1])) { - int last = find_real_in(field, 1); - string real_string = field.substr(1, last - 1); - return atof(real_string.c_str()); - } else { - return (double) parse_key(field); - } -} - - -// it is the responsibility of the caller to delete -// the seq -Alg_reader::Alg_reader(istream *a_file, Alg_seq_ptr new_seq) -{ - file = a_file; // save the file - line_parser_flag = false; - line_no = 0; - tsnum = 4; // default time signature - tsden = 4; - seq = new_seq; -} - - -Alg_error alg_read(istream &file, Alg_seq_ptr new_seq) - // read a sequence from allegro file -{ - assert(new_seq); - Alg_reader alg_reader(&file, new_seq); - bool err = alg_reader.parse(); - return (err ? alg_error_syntax : alg_no_error); -} - - -void Alg_reader::readline() -{ - // a word about memory management: this Alg_reader has a - // member variable input_line that holds a line of input - // it is reused for each line. input_line is parsed by - // line_parser, which holds a reference to input_line - line_parser_flag = false; - if (getline(*file, input_line)) { - line_parser.init(&input_line); - line_parser_flag = true; - error_flag = false; - } -} - - -Alg_parameters_ptr Alg_reader::process_attributes( - Alg_parameters_ptr attributes, double time) -{ - // print "process_attributes:", attributes - bool ts_flag = false; - if (attributes) { - Alg_parameters_ptr a; - bool in_seconds = seq->get_units_are_seconds(); - if ((a = Alg_parameters::remove_key(&attributes, "tempor"))) { - double tempo = a->parm.r; - seq->insert_tempo(tempo, seq->get_time_map()->time_to_beat(time)); - } - if ((a = Alg_parameters::remove_key(&attributes, "beatr"))) { - double beat = a->parm.r; - seq->insert_beat(time, beat); - } - if ((a = Alg_parameters::remove_key(&attributes, "timesig_numr"))) { - tsnum = a->parm.r; - ts_flag = true; - } - if ((a = Alg_parameters::remove_key(&attributes, "timesig_denr"))) { - tsden = a->parm.r; - ts_flag = true; - } - if (ts_flag) { - seq->set_time_sig(seq->get_time_map()->time_to_beat(time), - tsnum, tsden); - } - if (in_seconds) seq->convert_to_seconds(); - } - return attributes; // in case it was modified -} - - -bool Alg_reader::parse() -{ - int voice = 0; - int key = 60; - double loud = 100.0; - double pitch = 60.0; - double dur = 1.0; - double time = 0.0; - int track_num = 0; - seq->convert_to_seconds(); - //seq->set_real_dur(0.0); // just in case it's not initialized already - readline(); - bool valid = false; // ignore blank lines - while (line_parser_flag) { - bool time_flag = false; - bool next_flag = false; - double next; - bool voice_flag = false; - bool loud_flag = false; - bool dur_flag = false; - bool new_pitch_flag = false; // "P" syntax or "A"-"G" syntax - double new_pitch = 0.0; - bool new_key_flag = false; // "K" syntax - int new_key = 0; - Alg_parameters_ptr attributes = NULL; - if (line_parser.peek() == '#') { - // look for #track - line_parser.get_nonspace_quoted(field); - if (streql(field.c_str(), "#track")) { - line_parser.get_nonspace_quoted(field); // number - field.insert(0, " "); // need char at beginning because - // parse_int ignores the first character of the argument - track_num = parse_int(field); - seq->add_track(track_num); - } - // maybe we have a sequence or track name - line_parser.get_remainder(field); - // if there is a non-space character after #track n then - // use it as sequence or track name. Note that because we - // skip over spaces, a sequence or track name cannot begin - // with leading blanks. Another decision is that the name - // must be at time zero - if (field.length() > 0) { - // insert the field as sequence name or track name - Alg_update_ptr update = new Alg_update; - update->chan = -1; - update->time = 0; - update->set_identifier(-1); - // sequence name is whatever is on track 0 - // other tracks have track names - const char *attr = - (track_num == 0 ? "seqnames" : "tracknames"); - update->parameter.set_attr(symbol_table.insert_string(attr)); - update->parameter.s = heapify(field.c_str()); - seq->add_event(update, track_num); - } - } else { - // we must have a track to insert into - if (seq->tracks() == 0) seq->add_track(0); - line_parser.get_nonspace_quoted(field); - char pk = line_parser.peek(); - // attributes are parsed as two adjacent nonspace_quoted tokens - // so we have to conditionally call get_nonspace_quoted() again - if (pk && !isspace(pk)) { - string field2; - line_parser.get_nonspace_quoted(field2); - field.append(field2); - } - while (field[0]) { - char first = toupper(field[0]); - if (strchr("ABCDEFGKLPUSIQHW-", first)) { - valid = true; // it's a note or event - } - if (first == 'V') { - if (voice_flag) { - parse_error(field, 0, "Voice specified twice"); - } else { - voice = parse_chan(field); - } - voice_flag = true; - } else if (first == 'T') { - if (time_flag) { - parse_error(field, 0, "Time specified twice"); - } else { - time = parse_dur(field, 0.0); - } - time_flag = true; - } else if (first == 'N') { - if (next_flag) { - parse_error(field, 0, "Next specified twice"); - } else { - next = parse_dur(field, time); - } - next_flag = true; - } else if (first == 'K') { - if (new_key_flag) { - parse_error(field, 0, "Key specified twice"); - } else { - new_key = parse_key(field); - new_key_flag = true; - } - } else if (first == 'L') { - if (loud_flag) { - parse_error(field, 0, "Loudness specified twice"); - } else { - loud = parse_loud(field); - } - loud_flag = true; - } else if (first == 'P') { - if (new_pitch_flag) { - parse_error(field, 0, "Pitch specified twice"); - } else { - new_pitch = parse_pitch(field); - new_pitch_flag = true; - } - } else if (first == 'U') { - if (dur_flag) { - parse_error(field, 0, "Dur specified twice"); - } else { - dur = parse_dur(field, time); - dur_flag = true; - } - } else if (strchr("SIQHW", first)) { - if (dur_flag) { - parse_error(field, 0, "Dur specified twice"); - } else { - // prepend 'U' to field, copy EOS too - field.insert(0, 1, 'U'); - dur = parse_dur(field, time); - dur_flag = true; - } - } else if (strchr("ABCDEFG", first)) { - if (new_pitch_flag) { - parse_error(field, 0, "Pitch specified twice"); - } else { - // prepend 'P' to field - field.insert(0, 1, 'P'); - new_pitch = parse_pitch(field); - new_pitch_flag = true; - } - } else if (first == '-') { - Alg_parameter parm; - if (parse_attribute(field, &parm)) { // enter attribute-value pair - attributes = new Alg_parameters(attributes); - attributes->parm = parm; - parm.s = NULL; // protect string from deletion by destructor - } - } else { - parse_error(field, 0, "Unknown field"); - } - - if (error_flag) { - field[0] = 0; // exit the loop - } else { - line_parser.get_nonspace_quoted(field); - pk = line_parser.peek(); - // attributes are parsed as two adjacent nonspace_quoted - // tokens so we have to conditionally call - // get_nonspace_quoted() again - if (pk && !isspace(pk)) { - string field2; - line_parser.get_nonspace_quoted(field2); - field.append(field2); - } - } - } - // a case analysis: - // Key < 128 implies pitch unless pitch is explicitly given - // Pitch implies Key unless key is explicitly given, - // Pitch is rounded to nearest integer to determine the Key - // if necessary, so MIDI files will lose the pitch fraction - // A-G is a Pitch specification (therefore it implies Key) - // K60 P60 -- both are specified, use 'em - // K60 P60 C4 -- overconstrained, an error - // K60 C4 -- OK, but K60 is already implied by C4 - // K60 -- OK, pitch is 60 - // C4 P60 -- over constrained - // P60 -- OK, key is 60 - // P60.1 -- OK, key is 60 - // C4 -- OK, key is 60, pitch is 60 - // -- OK, key and pitch from before - // K200 P60 -- ok, pitch is 60 - // K200 with neither P60 nor C4 uses - // pitch from before - - // figure out what the key/instance is: - if (new_key_flag) { // it was directly specified - key = new_key; - } else if (new_pitch_flag) { - // pitch was specified, but key was not; get key from pitch - key = (int) (new_pitch + 0.5); // round to integer key number - } - if (new_pitch_flag) { - pitch = new_pitch; - } else if (key < 128 && new_key_flag) { - // no explicit pitch, but key < 128, so it implies pitch - pitch = key; - new_pitch_flag = true; - } - // now we've acquired new parameters - // if (it is a note, then enter the note - if (valid) { - // change tempo or beat - attributes = process_attributes(attributes, time); - // if there's a duration or pitch, make a note: - if (new_pitch_flag || dur_flag) { - Alg_note_ptr note_ptr = new Alg_note; - note_ptr->chan = voice; - note_ptr->time = time; - note_ptr->dur = dur; - note_ptr->set_identifier(key); - note_ptr->pitch = pitch; - note_ptr->loud = loud; - note_ptr->parameters = attributes; - seq->add_event(note_ptr, track_num); // sort later - if (seq->get_real_dur() < (time + dur)) seq->set_real_dur(time + dur); - } else { - int update_key = -1; - // key must appear explicitly; otherwise - // update applies to channel - if (new_key_flag) { - update_key = key; - } - if (loud_flag) { - Alg_update_ptr new_upd = new Alg_update; - new_upd->chan = voice; - new_upd->time = time; - new_upd->set_identifier(update_key); - new_upd->parameter.set_attr(symbol_table.insert_string("loudr")); - new_upd->parameter.r = pitch; - seq->add_event(new_upd, track_num); - if (seq->get_real_dur() < time) seq->set_real_dur(time); - } - if (attributes) { - while (attributes) { - Alg_update_ptr new_upd = new Alg_update; - new_upd->chan = voice; - new_upd->time = time; - new_upd->set_identifier(update_key); - new_upd->parameter = attributes->parm; - seq->add_event(new_upd, track_num); - Alg_parameters_ptr p = attributes; - attributes = attributes->next; - p->parm.s = NULL; // so we don't delete the string - delete p; - } - } - } - if (next_flag) { - time = time + next; - } else if (dur_flag || new_pitch_flag) { // a note: incr by dur - time = time + dur; - } - } - } - readline(); - } - if (!error_flag) { // why not convert even if there was an error? -RBD - seq->convert_to_seconds(); // make sure format is correct - } - // real_dur is valid, translate to beat_dur - seq->set_beat_dur((seq->get_time_map())->time_to_beat(seq->get_real_dur())); - return error_flag; -} - - -long Alg_reader::parse_chan(string &field) -{ - const char *int_string = field.c_str() + 1; - const char *msg = "Integer or - expected"; - const char *p = int_string; - char c; - // check that all chars in int_string are digits or '-': - while ((c = *p++)) { - if (!isdigit(c) && c != '-') { - parse_error(field, p - field.c_str() - 1, msg); - return 0; - } - } - p--; // p now points to end-of-string character - if (p - int_string == 0) { - // bad: string length is zero - parse_error(field, 1, msg); - return 0; - } - if (p - int_string == 1 && int_string[0] == '-') { - // special case: entire string is "-", interpret as -1 - return -1; - } - return atoi(int_string); -} - - -long Alg_reader::parse_int(string &field) -{ - const char *int_string = field.c_str() + 1; - const char *msg = "Integer expected"; - const char *p = int_string; - char c; - // check that all chars in int_string are digits: - while ((c = *p++)) { - if (!isdigit(c)) { - parse_error(field, p - field.c_str() - 1, msg); - return 0; - } - } - p--; // p now points to end-of-string character - if (p - int_string == 0) { - // bad: string length is zero - parse_error(field, 1, msg); - return 0; - } - return atoi(int_string); -} - - -int Alg_reader::find_real_in(string &field, int n) -{ - // scans from offset n to the end of a real constant - bool decimal = false; - int len = field.length(); - for (int i = n; i < len; i++) { - char c = field[i]; - if (!isdigit(c)) { - if (c == '.' && !decimal) { - decimal = true; - } else { - return i; - } - } - } - return field.length(); -} - - -double Alg_reader::parse_real(string &field) -{ - const char *msg = "Real expected"; - int last = find_real_in(field, 1); - string real_string = field.substr(1, last - 1); - if (last <= 1 || last < (int) field.length()) { - parse_error(field, 1, msg); - return 0; - } - return atof(real_string.c_str()); -} - - -void Alg_reader::parse_error(string &field, long offset, const char *message) -{ - int position = line_parser.pos - field.length() + offset; - error_flag = true; - puts(line_parser.str->c_str()); - for (int i = 0; i < position; i++) { - putc(' ', stdout); - } - putc('^', stdout); - printf(" %s\n", message); -} - - -double duration_lookup[] = { 0.25, 0.5, 1.0, 2.0, 4.0 }; - - -double Alg_reader::parse_dur(string &field, double base) -{ - const char *msg = "Duration expected"; - const char *durs = "SIQHW"; - const char *p; - int last; - double dur; - if (field.length() < 2) { - // fall through to error message - return -1; - } else if (isdigit(field[1])) { - last = find_real_in(field, 1); - string real_string = field.substr(1, last - 1); - dur = atof(real_string.c_str()); - // convert dur from seconds to beats - dur = seq->get_time_map()->time_to_beat(base + dur) - - seq->get_time_map()->time_to_beat(base); - } else if ((p = strchr(durs, toupper(field[1])))) { - dur = duration_lookup[p - durs]; - last = 2; - } else { - parse_error(field, 1, msg); - return 0; - } - dur = parse_after_dur(dur, field, last, base); - dur = seq->get_time_map()->beat_to_time( - seq->get_time_map()->time_to_beat(base) + dur) - base; - return dur; -} - - -double Alg_reader::parse_after_dur(double dur, string &field, - int n, double base) -{ - if ((int) field.length() == n) { - return dur; - } - if (toupper(field[n]) == 'T') { - return parse_after_dur(dur * 2/3, field, n + 1, base); - } - if (field[n] == '.') { - return parse_after_dur(dur * 1.5, field, n + 1, base); - } - if (isdigit(field[n])) { - int last = find_real_in(field, n); - string a_string = field.substr(n, last - n); - double f = atof(a_string.c_str()); - return parse_after_dur(dur * f, field, last, base); - } - if (field[n] == '+') { - string a_string = field.substr(n + 1); - return dur + parse_dur( - a_string, seq->get_time_map()->beat_to_time( - seq->get_time_map()->time_to_beat(base) + dur)); - } - parse_error(field, n, "Unexpected character in duration"); - return dur; -} - -struct loud_lookup_struct { - const char *str; - int val; -} loud_lookup[] = { {"FFF", 127}, {"FF", 120}, {"F", 110}, {"MF", 100}, - {"MP", 90}, {"P", 80}, {"PP", 70}, {"PPP", 60}, - {NULL, 0} }; - - -double Alg_reader::parse_loud(string &field) -{ - const char *msg = "Loudness expected"; - if (isdigit(field[1])) { - return parse_int(field); - } else { - string dyn = field.substr(1); - transform(dyn.begin(), dyn.end(), dyn.begin(), ::toupper); - for (int i = 0; loud_lookup[i].str; i++) { - if (streql(loud_lookup[i].str, dyn.c_str())) { - return (double) loud_lookup[i].val; - } - } - } - parse_error(field, 1, msg); - return 100.0; -} - - -int key_lookup[] = {21, 23, 12, 14, 16, 17, 19}; - - -// the field can be K or K[A-G] or P[A-G] -// (this can be called from parse_pitch() to handle [A-G]) -// Notice that the routine ignores the first character: K or P -// -long Alg_reader::parse_key(string &field) -{ - const char *msg = "Pitch expected"; - const char *pitches = "ABCDEFG"; - const char *p; - if (isdigit(field[1])) { - // This routine would not have been called if field = "P" - // so it must be "K" so must be an integer. - return parse_int(field); - } else if ((p = strchr(pitches, toupper(field[1])))) { - long key = key_lookup[p - pitches]; - key = parse_after_key(key, field, 2); - return key; - } - parse_error(field, 1, msg); - return 0; -} - - -long Alg_reader::parse_after_key(int key, string &field, int n) -{ - if ((int) field.length() == n) { - return key; - } - char c = toupper(field[n]); - if (c == 'S') { - return parse_after_key(key + 1, field, n + 1); - } - if (c == 'F') { - return parse_after_key(key - 1, field, n + 1); - } - if (isdigit(field[n])) { - int last = find_int_in(field, n); - string octave = field.substr(n, last - n); - int oct = atoi(octave.c_str()); - return parse_after_key(key + oct * 12, field, last); - } - parse_error(field, n, "Unexpected character in pitch"); - return key; -} - - -long Alg_reader::find_int_in(string &field, int n) -{ - while ((int) field.length() > n && isdigit(field[n])) { - n = n + 1; - } - return n; -} - - -bool Alg_reader::parse_attribute(string &field, Alg_parameter_ptr param) -{ - int i = 1; - while (i < (int) field.length()) { - if (field[i] == ':') { - string attr = field.substr(1, i - 1); - char type_char = field[i - 1]; - if (strchr("iarsl", type_char)) { - param->set_attr(symbol_table.insert_string(attr.c_str())); - parse_val(param, field, i + 1); - } else { - parse_error(field, 0, "attribute needs to end with typecode: i,a,r,s, or l"); - } - return !error_flag; - } - i = i + 1; - } - return false; -} - - -bool Alg_reader::parse_val(Alg_parameter_ptr param, string &s, int i) -{ - int len = (int) s.length(); - if (i >= len) { - return false; - } - if (s[i] == '"') { - if (!check_type('s', param)) { - return false; - } - // note: (len - i) includes 2 quote characters but no EOS character - // so total memory to allocate is (len - i) - 1 - char *r = new char[(len - i) - 1]; - strncpy(r, s.c_str() + i + 1, (len - i) - 2); - r[(len - i) - 2] = 0; // terminate the string - param->s = r; - } else if (s[i] == '\'') { - if (!check_type('a', param)) { - return false; - } - string r = s.substr(i + 1, len - i - 2); - param->a = symbol_table.insert_string(r.c_str()); - } else if (param->attr_type() == 'l') { - if (streql(s.c_str() + i, "true") || - streql(s.c_str() + i, "t")) { - param->l = true; - } else if (streql(s.c_str() + i, "false") || - streql(s.c_str() + i, "nil")) { - param->l = false; - } else return false; - } else if (isdigit(s[i]) || s[i] == '-' || s[i] == '.') { - int pos = i; - bool period = false; - if (s[pos] == '-') { - pos++; - } - while (pos < len) { - if (isdigit(s[pos])) { - ; - } else if (!period && s[pos] == '.') { - period = true; - } else { - parse_error(s, pos, "Unexpected char in number"); - return false; - } - pos = pos + 1; - } - string r = s.substr(i, len - i); - if (period) { - if (!check_type('r', param)) { - return false; - } - param->r = atof(r.c_str()); - } else { - if (param->attr_type() == 'r') { - param->r = atoi(r.c_str()); - } else if (!check_type('i', param)) { - return false; - } else { - param->i = atoi(r.c_str()); - } - } - } else { - parse_error(s, i, "invalid value"); - return false; - } - return true; -} - - -bool Alg_reader::check_type(char type_char, Alg_parameter_ptr param) -{ - return param->attr_type() == type_char; -} - - -//duration_lookup = {"S": 0.5, "I": 0.5, "Q": 1, "H": 2, "W": 4} -//key_lookup = {"C": 12, "D": 14, "E": 16, "F": 17, "G": 19, "A": 21, "B": 23} - -/* -def test(): - reader = Alg_reader(open("data\\test.gro", "r")) - reader.parse() - score = reader->seq.notes - print "score:", score - reader = nil -*/ diff --git a/plugins/MidiImport/portsmf/allegroserial.cpp b/plugins/MidiImport/portsmf/allegroserial.cpp deleted file mode 100644 index e6b52f339f1..00000000000 --- a/plugins/MidiImport/portsmf/allegroserial.cpp +++ /dev/null @@ -1,2 +0,0 @@ -// allegroserial.cpp -- convert track to memory buffer and back to structure - diff --git a/plugins/MidiImport/portsmf/allegrosmfrd.cpp b/plugins/MidiImport/portsmf/allegrosmfrd.cpp deleted file mode 100644 index 49e2ef03ee6..00000000000 --- a/plugins/MidiImport/portsmf/allegrosmfrd.cpp +++ /dev/null @@ -1,445 +0,0 @@ -// midifile reader - -#include "stdlib.h" -#include "stdio.h" -#include "string.h" -#include "debug.h" -#include -#include -#include "allegro.h" -#include "algsmfrd_internal.h" -#include "mfmidi.h" -#include "trace.h" - -using namespace std; - -typedef class Alg_pending { -public: - Alg_note_ptr note; - class Alg_pending *next; - Alg_pending(Alg_note_ptr n, class Alg_pending *list) { - note = n; next = list; } -} *Alg_pending_ptr; - - -class Alg_midifile_reader: public Midifile_reader { -public: - istream *file; - Alg_seq_ptr seq; - int divisions; - Alg_pending_ptr pending; - Alg_track_ptr track; - int track_number; // the number of the (current) track - // chan is actual_channel + channel_offset_per_track * track_num + - // channel_offset_per_track * port - long channel_offset_per_track; // used to encode track number into channel - // default is 0, set this to 0 to merge all tracks to 16 channels - long channel_offset_per_port; // used to encode port number into channel - // default is 16, set to 0 to ignore port prefix meta events - // while reading, this is channel_offset_per_track * track_num - int channel_offset; - - Alg_midifile_reader(istream &f, Alg_seq_ptr new_seq) { - file = &f; - pending = NULL; - seq = new_seq; - channel_offset_per_track = 0; - channel_offset_per_port = 16; - track_number = -1; // no tracks started yet, 1st will be #0 - meta_channel = -1; - port = 0; - } - // delete destroys the seq member as well, so set it to NULL if you - // copied the pointer elsewhere - ~Alg_midifile_reader(); - // the following is used to load the Alg_seq from the file: - bool parse(); - - void set_nomerge(bool flag) { Mf_nomerge = flag; } - void set_skipinit(bool flag) { Mf_skipinit = flag; } - long get_currtime() { return Mf_currtime; } - -protected: - int meta_channel; // the channel for meta events, set by MIDI chan prefix - int port; // value from the portprefix meta event - - double get_time(); - void update(int chan, int key, Alg_parameter_ptr param); - void *Mf_malloc(size_t size) { return malloc(size); } - void Mf_free(void *obj, size_t size) { free(obj); } - /* Methods to be called while processing the MIDI file. */ - void Mf_starttrack(); - void Mf_endtrack(); - int Mf_getc(); - void Mf_chanprefix(int chan); - void Mf_portprefix(int port); - void Mf_eot(); - void Mf_error(const char *); - void Mf_header(int,int,int); - void Mf_on(int,int,int); - void Mf_off(int,int,int); - void Mf_pressure(int,int,int); - void Mf_controller(int,int,int); - void Mf_pitchbend(int,int,int); - void Mf_program(int,int); - void Mf_chanpressure(int,int); - void binary_msg(int len, char *msg, const char *attr_string); - void Mf_sysex(int,char*); - void Mf_arbitrary(int,char*); - void Mf_metamisc(int,int,char*); - void Mf_seqnum(int); - void Mf_smpte(int,int,int,int,int); - void Mf_timesig(int,int,int,int); - void Mf_tempo(int); - void Mf_keysig(int,int); - void Mf_sqspecific(int,char*); - void Mf_text(int,int,char*); -}; - - -Alg_midifile_reader::~Alg_midifile_reader() -{ - while (pending) { - Alg_pending_ptr to_be_freed = pending; - pending = pending->next; - delete to_be_freed; - } - finalize(); // free Mf reader memory -} - - -bool Alg_midifile_reader::parse() -{ - channel_offset = 0; - seq->convert_to_beats(); - midifile(); - seq->set_real_dur(seq->get_time_map()->beat_to_time(seq->get_beat_dur())); - return midifile_error != 0; -} - - -void Alg_midifile_reader::Mf_starttrack() -{ - // printf("starting new track\n"); - // create a new track that will share the sequence time map - // since time is in beats, the seconds parameter is false - track_number++; - seq->add_track(track_number); // make sure track exists - track = seq->track(track_number); // keep pointer to current track - meta_channel = -1; - port = 0; -} - - -void Alg_midifile_reader::Mf_endtrack() -{ - // note: track is already part of seq, so do not add it here - // printf("finished track, length %d number %d\n", track->len, track_num / 100); - channel_offset += seq->channel_offset_per_track; - track = NULL; - double now = get_time(); - if (seq->get_beat_dur() < now) seq->set_beat_dur(now); - meta_channel = -1; - port = 0; -} - - -int Alg_midifile_reader::Mf_getc() -{ - return file->get(); -} - - -void Alg_midifile_reader::Mf_chanprefix(int chan) -{ - meta_channel = chan; -} - - -void Alg_midifile_reader::Mf_portprefix(int p) -{ - port = p; -} - - -void Alg_midifile_reader::Mf_eot() -{ - meta_channel = -1; - port = 0; -} - - -void Alg_midifile_reader::Mf_error(const char *msg) -{ - fprintf(stdout, "Midifile reader error: %s\n", msg); -} - - -void Alg_midifile_reader::Mf_header(int format, int ntrks, int division) -{ - if (format > 1) { - char msg[80]; - sprintf(msg, "file format %d not implemented", format); - Mf_error(msg); - } - divisions = division; -} - - -double Alg_midifile_reader::get_time() -{ - double beat = ((double) get_currtime()) / divisions; - return beat; -} - - -void Alg_midifile_reader::Mf_on(int chan, int key, int vel) -{ - assert(!seq->get_units_are_seconds()); - if (vel == 0) { - Mf_off(chan, key, vel); - return; - } - Alg_note_ptr note = new Alg_note(); - pending = new Alg_pending(note, pending); - /* trace("on: %d at %g\n", key, get_time()); */ - note->time = get_time(); - note->chan = chan + channel_offset + port * channel_offset_per_port; - note->dur = 0; - note->set_identifier(key); - note->pitch = (float) key; - note->loud = (float) vel; - track->append(note); - meta_channel = -1; -} - - -void Alg_midifile_reader::Mf_off(int chan, int key, int vel) -{ - double time = get_time(); - Alg_pending_ptr *p = &pending; - while (*p) { - if ((*p)->note->get_identifier() == key && - (*p)->note->chan == - chan + channel_offset + port * channel_offset_per_port) { - (*p)->note->dur = time - (*p)->note->time; - // trace("updated %d dur %g\n", (*p)->note->key, (*p)->note->dur); - Alg_pending_ptr to_be_freed = *p; - *p = to_be_freed->next; - delete to_be_freed; - } else { - p = &((*p)->next); - } - } - meta_channel = -1; -} - - -void Alg_midifile_reader::update(int chan, int key, Alg_parameter_ptr param) -{ - Alg_update_ptr update = new Alg_update; - update->time = get_time(); - update->chan = chan; - if (chan != -1) { - update->chan = chan + channel_offset + port * channel_offset_per_port; - } - update->set_identifier(key); - update->parameter = *param; - // prevent the destructor from destroying the string twice! - // the new Update takes the string from param - if (param->attr_type() == 's') param->s = NULL; - track->append(update); -} - - -void Alg_midifile_reader::Mf_pressure(int chan, int key, int val) -{ - Alg_parameter parameter; - parameter.set_attr(symbol_table.insert_string("pressurer")); - parameter.r = val / 127.0; - update(chan, key, ¶meter); - meta_channel = -1; -} - - -void Alg_midifile_reader::Mf_controller(int chan, int control, int val) -{ - Alg_parameter parameter; - char name[32]; - sprintf(name, "control%dr", control); - parameter.set_attr(symbol_table.insert_string(name)); - parameter.r = val / 127.0; - update(chan, -1, ¶meter); - meta_channel = -1; -} - - -void Alg_midifile_reader::Mf_pitchbend(int chan, int c1, int c2) -{ - Alg_parameter parameter; - parameter.set_attr(symbol_table.insert_string("bendr")); - parameter.r = ((c2 << 7) + c1) / 8192.0 - 1.0; - update(chan, -1, ¶meter); - meta_channel = -1; -} - - -void Alg_midifile_reader::Mf_program(int chan, int program) -{ - Alg_parameter parameter; - parameter.set_attr(symbol_table.insert_string("programi")); - parameter.i = program; - update(chan, -1, ¶meter); - meta_channel = -1; -} - - -void Alg_midifile_reader::Mf_chanpressure(int chan, int val) -{ - Alg_parameter parameter; - parameter.set_attr(symbol_table.insert_string("pressurer")); - parameter.r = val / 127.0; - update(chan, -1, ¶meter); - meta_channel = -1; -} - - -void Alg_midifile_reader::binary_msg(int len, char *msg, - const char *attr_string) -{ - Alg_parameter parameter; - char *hexstr = new char[len * 2 + 1]; - for (int i = 0; i < len; i++) { - sprintf(hexstr + 2 * i, "%02x", (0xFF & msg[i])); - } - parameter.s = hexstr; - parameter.set_attr(symbol_table.insert_string(attr_string)); - update(meta_channel, -1, ¶meter); -} - - -void Alg_midifile_reader::Mf_sysex(int len, char *msg) -{ - // sysex messages become updates with attribute sysexs and a hex string - binary_msg(len, msg, "sysexs"); -} - - -void Alg_midifile_reader::Mf_arbitrary(int len, char *msg) -{ - Mf_error("arbitrary data ignored"); -} - - -void Alg_midifile_reader::Mf_metamisc(int type, int len, char *msg) -{ - char text[128]; - sprintf(text, "metamsic data, type 0x%x, ignored", type); - Mf_error(text); -} - - -void Alg_midifile_reader::Mf_seqnum(int n) -{ - Mf_error("seqnum data ignored"); -} - - -static const char *fpsstr[4] = {"24", "25", "29.97", "30"}; - -void Alg_midifile_reader::Mf_smpte(int hours, int mins, int secs, - int frames, int subframes) -{ - // string will look like "24fps:01h:27m:07s:19.00f" - // 30fps (drop frame) is notated as "29.97fps" - char text[32]; - int fps = (hours >> 6) & 3; - hours &= 0x1F; - sprintf(text, "%sfps:%02dh:%02dm:%02ds:%02d.%02df", - fpsstr[fps], hours, mins, secs, frames, subframes); - Alg_parameter smpteoffset; - smpteoffset.s = heapify(text); - smpteoffset.set_attr(symbol_table.insert_string("smpteoffsets")); - update(meta_channel, -1, &smpteoffset); - // Mf_error("SMPTE data ignored"); -} - - -void Alg_midifile_reader::Mf_timesig(int i1, int i2, int i3, int i4) -{ - seq->set_time_sig(get_currtime() / divisions, i1, 1 << i2); -} - - -void Alg_midifile_reader::Mf_tempo(int tempo) -{ - double beat = get_currtime(); - beat = beat / divisions; // convert to quarters - // 6000000 us/min / n us/beat => beat / min - double bpm = 60000000.0 / tempo; - seq->insert_tempo(bpm, beat); -} - - -void Alg_midifile_reader::Mf_keysig(int key, int mode) -{ - Alg_parameter key_parm; - key_parm.set_attr(symbol_table.insert_string("keysigi")); - // use 0 for C major, 1 for G, -1 for F, etc., that is, - // the number of sharps, where flats are negative sharps - key_parm.i = key; //<<<---- fix this - // use -1 to mean "all channels" - update(meta_channel, -1, &key_parm); - Alg_parameter mode_parm; - mode_parm.set_attr(symbol_table.insert_string("modea")); - mode_parm.a = (mode == 0 ? symbol_table.insert_string("major") : - symbol_table.insert_string("minor")); - update(meta_channel, -1, &mode_parm); -} - - -void Alg_midifile_reader::Mf_sqspecific(int len, char *msg) -{ - // sequencer specific messages become updates with attribute sqspecifics - // and a hex string for the value - binary_msg(len, msg, "sqspecifics"); -} - - -char *heapify2(int len, char *s) -{ - char *h = new char[len + 1]; - memcpy(h, s, len); - h[len] = 0; - return h; -} - - -void Alg_midifile_reader::Mf_text(int type, int len, char *msg) -{ - Alg_parameter text; - text.s = heapify2(len, msg); - const char *attr = "miscs"; - if (type == 1) attr = "texts"; - else if (type == 2) attr = "copyrights"; - else if (type == 3) - attr = (track_number == 0 ? "seqnames" : "tracknames"); - else if (type == 4) attr = "instruments"; - else if (type == 5) attr = "lyrics"; - else if (type == 6) attr = "markers"; - else if (type == 7) attr = "cues"; - text.set_attr(symbol_table.insert_string(attr)); - update(meta_channel, -1, &text); -} - - -// parse file into a seq. -Alg_error alg_smf_read(istream &file, Alg_seq_ptr new_seq) -{ - assert(new_seq); - Alg_midifile_reader ar(file, new_seq); - bool err = ar.parse(); - ar.seq->set_real_dur(ar.seq->get_time_map()-> - beat_to_time(ar.seq->get_beat_dur())); - return (err ? alg_error_syntax : alg_no_error); -} diff --git a/plugins/MidiImport/portsmf/allegrosmfwr.cpp b/plugins/MidiImport/portsmf/allegrosmfwr.cpp deleted file mode 100644 index 5a76c44ed9f..00000000000 --- a/plugins/MidiImport/portsmf/allegrosmfwr.cpp +++ /dev/null @@ -1,649 +0,0 @@ -// allegrosmfwr.cpp -- Allegro Standard Midi File Write - -#include -#include -#include -#include -#include -#include -#include -using namespace std; -#include "allegro.h" - -// event_queue is a list element that keeps track of pending -// things to write to a track, including note-ons, note-offs, -// updates, tempo changes, and time signatures -// -class event_queue{ -public: - char type;//'n' for note, 'o' for off, 's' for time signature, - // 'c' for tempo changes - double time; - long index; //of the event in mSeq->notes - class event_queue *next; - event_queue(char t, double when, long x, class event_queue *n) { - type = t; time = when; index = x; next = n; } -}; - - -class Alg_smf_write { -public: - Alg_smf_write(Alg_seq_ptr seq); - ~Alg_smf_write(); - long channels_per_track; // used to encode track number into chan field - // chan is actual_channel + channels_per_track * track_number - // default is 100, set this to 0 to merge all tracks to 16 channels - - void write(ofstream &file /* , midiFileFormat = 1 */); - -private: - long previous_divs; // time in ticks of most recently written event - - void write_track(int i); - void write_tempo(int divs, int tempo); - void write_tempo_change(int i); - void write_time_signature(int i); - void write_note(Alg_note_ptr note, bool on); - void write_update(Alg_update_ptr update); - void write_text(Alg_update_ptr update, char type); - void write_binary(int type_byte, char *msg); - void write_midi_channel_prefix(Alg_update_ptr update); - void write_smpteoffset(Alg_update_ptr update, char *s); - void write_data(int data); - int to_midi_channel(int channel); - int to_track(int channel); - - ostream *out_file; - - Alg_seq_ptr seq; - - int num_tracks; // number of tracks not counting tempo track - int division; // divisions per quarter note, default = 120 - int initial_tempo; - - int timesig_num; // numerator of time signature - int timesig_den; // denominator of time signature - double timesig_when; // time of time signature - - int keysig; // number of sharps (+) or flats (-), -99 for undefined - char keysig_mode; // 'M' or 'm' for major/minor - double keysig_when; // time of key signature - - void write_delta(double event_time); - void write_varinum(int num); - void write_16bit(int num); - void write_24bit(int num); - void write_32bit(int num); -}; - -#define ROUND(x) (int) ((x)+0.5) - -Alg_smf_write::Alg_smf_write(Alg_seq_ptr a_seq) -{ - out_file = NULL; - - // at 100bpm (a nominal tempo value), we would like a division - // to represent 1ms of time. So - // d ticks/beat * 100 beats/min = 60,000 ms/min * 1 tick/ms - // solving for d, d = 600 - division = 600; // divisions per quarter note - timesig_num = timesig_den = 0; // initially undefined - keysig = -99; - keysig_mode = 0; - initial_tempo = 500000; - - seq = a_seq; - - previous_divs = 0; // used to compute deltas for midifile -} - - -Alg_smf_write::~Alg_smf_write() -{ -} - - -// sorting is quite subtle due to rounding -// For example, suppose times from a MIDI file are exact, but in -// decimal round to TW0.4167 Q0.3333. Since the time in whole notes -// rounded up, this note will start late. Even though the duration -// rounded down, the amount is 1/4 as much because units are quarter -// notes. Therefore, the total roundup is 0.0001 beats. This is -// enough to cause the note to sort later in the queue, perhaps -// coming after a new note-on on the same pitch, and resulting in -// a turning on-off, on-off into on, on, off, off if data is moved -// to Allegro (ascii) format with rounding and then back to SMF. -// -// The solution here is to consider things that round to the same -// tick to be simultaneous. Then, be sure to deal with note-offs -// before note-ons. We're going to do that by using event_queue -// times that are rounded to the nearest tick time. Except note-offs -// are going to go in with times that are 1/4 tick earlier so they -// get scheduled first, but still end up on the same tick. -// -event_queue* push(event_queue *queue, event_queue *event) -{ - // printf("push: %.6g, %c, %d\n", event->time, event->type, event->index); - if (queue == NULL) { - event->next = NULL; - return event; - } - - event_queue *marker1 = NULL; - event_queue *marker2 = queue; - while (marker2 != NULL && marker2->time <= event->time) { - marker1 = marker2; - marker2 = marker2->next; - } - event->next = marker2; - if (marker1 != NULL) { - marker1->next=event; - return queue; - } else return event; -} - - -void print_queue(event_queue *q) -{ - printf("Printing queue. . .\n"); - event_queue *q2=q; - while (q2) { - printf("%c at %f ;", q2->type, q2->time); - q2 = q2->next; - } - printf("\nDone printing.\n"); -} - - -void Alg_smf_write::write_note(Alg_note_ptr note, bool on) -{ - double event_time = (on ? note->time : note->time + note->dur); - write_delta(event_time); - - //printf("deltaDivisions: %d, beats elapsed: %g, on? %c\n", deltaDivisions, note->time, on); - - char chan = (note->chan & 15); - int pitch = int(note->pitch + 0.5); - if (pitch < 0) { - pitch = pitch % 12; - } else if (pitch > 127) { - pitch = (pitch % 12) + 120; // put pitch in 10th octave - if (pitch > 127) pitch -= 12; // or 9th octave - } - out_file->put(0x90 + chan); - out_file->put(pitch); - if (on) { - int vel = (int) note->loud; - if (vel <= 0) vel = 1; - write_data(vel); - } else out_file->put(0); // note-off indicated by velocty zero -} - - -void Alg_smf_write::write_midi_channel_prefix(Alg_update_ptr update) -{ - if (update->chan >= 0) { // write MIDI Channel Prefix - write_delta(update->time); - out_file->put(0xFF); // Meta Event - out_file->put(0x20); // Type code for MIDI Channel Prefix - out_file->put(1); // length - out_file->put(to_midi_channel(update->chan)); - // one thing odd about the Std MIDI File spec is that once - // you turn on MIDI Channel Prefix, there seems to be no - // way to cancel it unless a non-Meta event shows up. We - // don't do any analysis to avoid assigning channels to - // meta events. - } -} - - -void Alg_smf_write::write_text(Alg_update_ptr update, char type) -{ - write_midi_channel_prefix(update); - write_delta(update->time); - out_file->put(0xFF); - out_file->put(type); - out_file->put((char) strlen(update->parameter.s)); - *out_file << update->parameter.s; -} - - -void Alg_smf_write::write_smpteoffset(Alg_update_ptr update, char *s) -{ - write_midi_channel_prefix(update); - write_delta(update->time); - out_file->put(0xFF); // meta event - out_file->put(0x54); // smpte offset type code - out_file->put(5); // length - for (int i = 0; i < 5; i++) *out_file << s[i]; -} - - -// write_data - limit data to the range of [0...127] and write it -void Alg_smf_write::write_data(int data) -{ - if (data < 0) data = 0; - else if (data > 0x7F) data = 0x7F; - - out_file->put(data); -} - - -int Alg_smf_write::to_midi_channel(int channel) -{ - // allegro track number is stored as multiple of 100 - // also mask off all but 4 channel bits just in case - if (channels_per_track > 0) channel %= channels_per_track; - return channel & 0xF; -} - - -int Alg_smf_write::to_track(int channel) -{ - if (channel == -1) return 0; - return channel / channels_per_track; -} - - -static char hex_to_nibble(char c) -{ - if (isalpha(c)) { - return 10 + (toupper(c) - 'A'); - } else { - return c - '0'; - } -} - - -static char hex_to_char(char *s) -{ - return (hex_to_nibble(s[0]) << 4) + hex_to_nibble(s[1]); -} - - -void Alg_smf_write::write_binary(int type_byte, char *msg) -{ - int len = strlen(msg) / 2; - out_file->put(type_byte); - write_varinum(len); - for (int i = 0; i < len; i++) { - out_file->put(hex_to_char(msg)); - msg += 2; - } -} - - -void Alg_smf_write::write_update(Alg_update_ptr update) -{ - char *name = update->parameter.attr_name(); - - /****Non-Meta Events****/ - if (!strcmp(name, "pressurer")) { - write_delta(update->time); - if (update->get_identifier() < 0) { // channel pressure message - out_file->put(0xD0 + to_midi_channel(update->chan)); - write_data((int)(update->parameter.r * 127)); - } else { // just 1 key -- poly pressure - out_file->put(0xA0 + to_midi_channel(update->chan)); - write_data(update->get_identifier()); - write_data((int)(update->parameter.r * 127)); - } - } else if (!strcmp(name, "programi")) { - write_delta(update->time); - out_file->put(0xC0 + to_midi_channel(update->chan)); - write_data(update->parameter.i); - } else if (!strcmp(name, "bendr")) { - int temp = ROUND(0x2000 * (update->parameter.r + 1)); - if (temp > 0x3fff) temp = 0x3fff; // 14 bits maximum - if (temp < 0) temp = 0; - int c1 = temp & 0x7F; // low 7 bits - int c2 = temp >> 7; // high 7 bits - write_delta(update->time); - out_file->put(0xE0 + to_midi_channel(update->chan)); - write_data(c1); - write_data(c2); - } else if (!strncmp(name, "control", 7) && - update->parameter.attr_type() == 'r') { - int ctrlnum = atoi(name + 7); - int val = ROUND(update->parameter.r * 127); - write_delta(update->time); - out_file->put(0xB0 + to_midi_channel(update->chan)); - write_data(ctrlnum); - write_data(val); - } else if (!strcmp(name, "sysexs") && - update->parameter.attr_type() == 's') { - char *s = update->parameter.s; - if (s[0] && s[1] && toupper(s[0]) == 'F' && s[1] == '0') { - s += 2; // skip the initial "F0" byte in message: it is implied - } - write_delta(update->time); - write_binary(0xF0, s); - } else if (!strcmp(name, "sqspecifics") && - update->parameter.attr_type() == 's') { - char *s = update->parameter.s; - write_delta(update->time); - out_file->put(0xFF); - write_binary(0x7F, s); - - /****Text Events****/ - } else if (!strcmp(name, "texts")) { - write_text(update, 0x01); - } else if (!strcmp(name, "copyrights")) { - write_text(update, 0x02); - } else if (!strcmp(name, "seqnames") || !strcmp(name, "tracknames")) { - write_text(update, 0x03); - } else if (!strcmp(name, "instruments")) { - write_text(update, 0x04); - } else if (!strcmp(name, "lyrics")) { - write_text(update, 0x05); - } else if (!strcmp(name, "markers")) { - write_text(update, 0x06); - } else if (!strcmp(name, "cues")) { - write_text(update, 0x07); - } else if (!strcmp(name, "miscs")) { - write_text(update, 0x08); - - /****Other Events****/ - } else if (!strcmp(name, "smpteoffsets")) { -#define decimal(p) (((p)[0] - '0') * 10 + ((p)[1] - '0')) - // smpteoffset is specified as "24fps:00h:10m:00s:11.00f" - // the following simple parser does not reject all badly - // formatted strings, but it should parse good strings ok - char *s = update->parameter.s; - int len = strlen(s); - char smpteoffset[5]; - if (len < 24) return; // not long enough, must be bad format - int fps = 0; - if (s[0] == '2') { - if (s[1] == '4') fps = 0; - else if (s[1] == '5') fps = 1; - else if (s[1] == '9') { - fps = 2; - if (len != 27) return; // not right length - s += 3; // cancel effect of longer string - } - } else fps = 3; - s += 6; int hours = decimal(s); - s += 4; int mins = decimal(s); - s += 4; int secs = decimal(s); - s += 4; int frames = decimal(s); - s += 3; int subframes = decimal(s); - smpteoffset[0] = (fps << 6) + hours; - smpteoffset[1] = mins; - smpteoffset[2] = secs; - smpteoffset[3] = frames; - smpteoffset[4] = subframes; - write_smpteoffset(update, smpteoffset); - - // key signature is special because it takes two events in the Alg_seq - // structure to make one midi file event. When we encounter one or - // the other event, we'll just record it in the Alg_smf_write object. - // After both events are seen, we write the data. (See below.) - } else if (!strcmp(name, "keysigi")) { - keysig = update->parameter.i; - keysig_when = update->time; - } else if (!strcmp(name, "modea")) { - if (!strcmp(alg_attr_name(update->parameter.a), "major")) - keysig_mode = 'M'; - else keysig_mode = 'm'; - keysig_when = update->time; - } - if (keysig != -99 && keysig_mode) { // write when both are defined - write_delta(keysig_when); - out_file->put(0xFF); - out_file->put(0x59); - out_file->put(2); - // mask off high bits so that this value appears to be positive - // i.e. -1 -> 0xFF (otherwise, write_data will clip -1 to 0) - out_file->put(keysig & 0xFF); - out_file->put(keysig_mode == 'm'); - keysig = -99; - keysig_mode = false; - } - //printf("Update: %s, key: %g\n", update->parameter.attr_name(), update->key); -} - - -// see notes on event_queue::push, TICK_TIME converts from beat to -// the number of the nearest tick. The second parameter is an offset in -// quarter ticks. By scheduling with -1, note-offs should get dispatched -// first. Note that TICK_TIME only determines the order of events, so -// it is ok to change units from beats to ticks, saving a divide. -#define TICK_TIME(t, o) (ROUND((t) * division) + 0.25 * (o)) - -void Alg_smf_write::write_track(int i) -{ - int j = 0; // note index - Alg_events ¬es = seq->track_list[i]; - event_queue *pending = NULL; - if (notes.length() > 0) { - pending = new event_queue('n', TICK_TIME(notes[j]->time, 0), 0, NULL); - } - if (i == 0) { // track 0 may have tempo and timesig info - if (seq->get_time_map()->last_tempo_flag || seq->get_time_map()->beats.len > 0) { - pending = push(pending, new event_queue('c', 0.0, 0, NULL)); - } - if (seq->time_sig.length() > 0) { - pending = push(pending, new event_queue('s', - TICK_TIME(seq->time_sig[0].beat, 0), 0, NULL)); - } - } - while (pending) { - event_queue *current = pending; - pending = pending->next; - if (current->type == 'n') { - Alg_note_ptr n = (Alg_note_ptr) notes[current->index]; - if (n->is_note()) { - write_note(n, true); - pending = push(pending, new event_queue('o', - TICK_TIME(n->time + n->dur, -1), current->index, NULL)); - } else if (n->is_update()) { - Alg_update_ptr u = (Alg_update_ptr) n; - write_update(u); - } - int next = current->index + 1; - if (next < notes.length()) { - current->time = TICK_TIME(notes[next]->time, 0); - current->index = next; - pending = push(pending, current); - } - } else if (current->type == 'o') { //note-off - Alg_note_ptr n = (Alg_note_ptr) notes[current->index]; - write_note(n, false); - delete current; - } else if (current->type == 'c') { // tempo change - write_tempo_change(current->index); - current->index++; // -R - if (current->index < seq->get_time_map()->beats.len) { - current->time = - TICK_TIME(seq->get_time_map()-> - beats[current->index].beat, 0); - pending = push(pending, current); - } else { - delete current; - } - } else if (current->type == 's') { // time sig - write_time_signature(current->index); - current->index++; - if (current->index < seq->time_sig.length()) { - current->time = - TICK_TIME(seq->time_sig[current->index].beat, 0); - pending = push(pending, current); - } else { - delete current; - } - } - } -} - - -void Alg_smf_write::write_tempo(int divs, int tempo) -{ - // printf("Inserting tempo %f after %f clocks.\n", tempo, delta); - write_varinum(divs - previous_divs); - previous_divs = divs; - out_file->put(0xFF); - out_file->put(0x51); - out_file->put(0x03); - write_24bit((int)tempo); -} - - -void Alg_smf_write::write_tempo_change(int i) - // i is index of tempo map -{ - // extract tempo map - Alg_beats &b = seq->get_time_map()->beats; - double tempo; - long divs; - if (i < seq->get_time_map()->beats.len - 1) { - tempo = 1000000 * ((b[i+1].time - b[i].time) / - (b[i+1].beat - b[i].beat)); - divs = ROUND(b[i].beat * division); - write_tempo(divs, ROUND(tempo)); - } else if (seq->get_time_map()->last_tempo_flag) { // write the final tempo - divs = ROUND(division * b[i].beat); - tempo = (1000000.0 / seq->get_time_map()->last_tempo); - write_tempo(divs, ROUND(tempo)); - } -} - - -void Alg_smf_write::write_time_signature(int i) -{ - Alg_time_sigs &ts = seq->time_sig; - // write the time signature - long divs = ROUND(ts[i].beat * division); - write_varinum(divs - previous_divs); - out_file->put(0xFF); - out_file->put(0x58); // time signature - out_file->put(4); // length of message - out_file->put(ROUND(ts[i].num)); - int den = ROUND(ts[i].den); - int den_byte = 0; - while (den > 1) { // compute the log2 of denominator - den_byte++; - den >>= 1; - } - out_file->put(den_byte); - out_file->put(24); // clocks per quarter - out_file->put(8); // 32nd notes per 24 clocks -} - - - -void Alg_smf_write::write(ofstream &file) -{ - int track_len_offset; - int track_end_offset; - int track_len; - - out_file = &file; - - // Header - file << "MThd"; - - write_32bit(6); // chunk length - - write_16bit(1); // format 1 MIDI file - - write_16bit(seq->tracks()); // number of tracks - write_16bit(division); // divisions per quarter note - - - // write_ all tracks - seq->convert_to_beats(); - int i; - for (i = 0; i < seq->tracks(); i++) { - previous_divs = 0; - *out_file << "MTrk"; - track_len_offset = out_file->tellp(); - write_32bit(0); // track len placeholder - - write_track(i); - - // End of track event - write_varinum(0); // delta time - out_file->put(0xFF); - out_file->put(0x2F); - out_file->put(0x00); - - // Go back and write in the length of the track - track_end_offset = out_file->tellp(); - track_len = track_end_offset - track_len_offset - 4; - out_file->seekp(track_len_offset); - write_32bit(track_len); - out_file->seekp(track_end_offset); - } -} - - -void Alg_smf_write::write_16bit(int num) -{ - out_file->put((num & 0xFF00) >> 8); - out_file->put(num & 0xFF); -} - -void Alg_smf_write::write_24bit(int num) -{ - out_file->put((num & 0xFF0000) >> 16); - out_file->put((num & 0xFF00) >> 8); - out_file->put((num & 0xFF)); -} - -void Alg_smf_write::write_32bit(int num) -{ - out_file->put((num & 0xFF000000) >> 24); - out_file->put((num & 0xFF0000) >> 16); - out_file->put((num & 0xFF00) >> 8); - out_file->put((num & 0xFF)); -} - - -void Alg_smf_write::write_delta(double event_time) -{ - // divisions is ideal absolute time in divisions - long divisions = ROUND(division * event_time); - long delta_divs = divisions - previous_divs; - write_varinum(delta_divs); - previous_divs = divisions; -} - - -void Alg_smf_write::write_varinum(int value) -{ - if(value<0) value=0;//this line should not have to be here! - int buffer; - - buffer = value & 0x7f; - while ((value >>= 7) > 0) { - buffer <<= 8; - buffer |= 0x80; - buffer += (value & 0x7f); - } - - for(;;) { - out_file->put(buffer); - if (buffer & 0x80) - buffer >>= 8; - else - break; - } -} - - -void Alg_seq::smf_write(ofstream &file) -{ - Alg_smf_write writer(this); - writer.write(file); -} - -bool Alg_seq::smf_write(const char *filename) -{ - ofstream outf(filename, ios::binary | ios::out); - if (outf.fail()) return false; - smf_write(outf); - outf.close(); - return true; -} - diff --git a/plugins/MidiImport/portsmf/allegrowr.cpp b/plugins/MidiImport/portsmf/allegrowr.cpp deleted file mode 100644 index 3b266f84cac..00000000000 --- a/plugins/MidiImport/portsmf/allegrowr.cpp +++ /dev/null @@ -1,181 +0,0 @@ -// allegrowr.cpp -- write sequence to an Allegro file (text) - -#include "debug.h" -#include "stdlib.h" -#include -#include -#include -#include -#include -#include "memory.h" -using namespace std; -#include "strparse.h" -#include "allegro.h" - -// Note about precision: %g prints 6 significant digits. For 1ms precision, -// the maximum magnitude is 999.999, i.e. 1000s < 17minutes. For anything -// over 1000s, time in seconds will be printed with 10ms precision, which -// is not good. Therefore, times and durations are printed as %.4d, which -// gives 100us precision. -// The following define allows you to change this decision: -/* #define TIMFMT "%.4d" */ -#define TIMPREC 4 -#define TIMFMT fixed << setprecision(TIMPREC) -#define GFMT resetiosflags(ios::floatfield) << setprecision(6) - -void parameter_print(ostream &file, Alg_parameter_ptr p) -{ - file << " -" << p->attr_name() << ":"; - switch (p->attr_type()) { - case 'a': - file << "'" << alg_attr_name(p->a) << "'"; - break; - case 'i': - file << p->i; - break; - case 'l': - file << (p->l ? "true" : "false"); - break; - case 'r': - file << p->r; - break; - case 's': { - string str; - string_escape(str, p->s, "\""); - file << str; - break; - } - } -} - -Alg_event_ptr Alg_seq::write_track_name(ostream &file, int n, - Alg_events &events) -// write #track -// if we write the name on the "#track" line, then we do *not* want -// to write again as an update: "-seqnames:"Jordu", so if we do -// find a name and write it, return a pointer to it so the track -// writer knows what update (if any) to skip -{ - Alg_event_ptr e = NULL; - file << "#track " << n; - const char *attr = symbol_table.insert_string( - n == 0 ? "seqnames" : "tracknames"); - // search for name in events with timestamp of 0 - for (int i = 0; i < events.length(); i++) { - e = events[i]; - if (e->time > 0) break; - if (e->is_update()) { - Alg_update_ptr u = (Alg_update_ptr) e; - if (u->parameter.attr == attr) { - file << " " << u->parameter.s; - break; - } - } - } - file << endl; - return e; -} - - -void Alg_seq::write(ostream &file, bool in_secs) -{ - int i, j; - if (in_secs) convert_to_seconds(); - else convert_to_beats(); - Alg_event_ptr update_to_skip = write_track_name(file, 0, track_list[0]); - Alg_beats &beats = time_map->beats; - for (i = 0; i < beats.len - 1; i++) { - Alg_beat_ptr b = &(beats[i]); - if (in_secs) { - file << "T" << TIMFMT << b->time; - } else { - file << "TW" << TIMFMT << b->beat / 4; - } - double tempo = (beats[i + 1].beat - b->beat) / - (beats[i + 1].time - beats[i].time); - file << " -tempor:" << GFMT << tempo * 60 << "\n"; - } - if (time_map->last_tempo_flag) { // we have final tempo: - Alg_beat_ptr b = &(beats[beats.len - 1]); - if (in_secs) { - file << "T" << TIMFMT << b->time; - } else { - file << "TW" << TIMFMT << b->beat / 4; - } - file << " -tempor:" << GFMT << time_map->last_tempo * 60.0 << "\n"; - } - - // write the time signatures - for (i = 0; i < time_sig.length(); i++) { - Alg_time_sig &ts = time_sig[i]; - double time = ts.beat; - if (in_secs) { - file << "T" << TIMFMT << time << " V- -timesig_numr:" << - GFMT << ts.num << "\n"; - file << "T" << TIMFMT << time << " V- -timesig_denr:" << - GFMT << ts.den << "\n"; - } else { - double wholes = ts.beat / 4; - file << "TW" << TIMFMT << wholes << " V- -timesig_numr:" << - GFMT << ts.num << "\n"; - file << "TW" << TIMFMT << wholes << " V- -timesig_denr:" << - GFMT << ts.den << "\n"; - } - } - - for (j = 0; j < track_list.length(); j++) { - Alg_events ¬es = track_list[j]; - if (j != 0) update_to_skip = write_track_name(file, j, notes); - // now write the notes at beat positions - for (i = 0; i < notes.length(); i++) { - Alg_event_ptr e = notes[i]; - // if we already wrote this event as a track or sequence name, - // do not write it again - if (e == update_to_skip) continue; - double start = e->time; - if (in_secs) { - file << "T" << TIMFMT << start; - } else { - file << "TW" << TIMFMT << start / 4; - } - // write the channel as Vn or V- - if (e->chan == -1) file << " V-"; - else file << " V" << e->chan; - // write the note or update data - if (e->is_note()) { - Alg_note_ptr n = (Alg_note_ptr) e; - double dur = n->dur; - file << " K" << n->get_identifier() << - " P" << GFMT << n->pitch; - if (in_secs) { - file << " U" << TIMFMT << dur; - } else { - file << " Q" << TIMFMT << dur; - } - file << " L" << GFMT << n->loud; - Alg_parameters_ptr p = n->parameters; - while (p) { - parameter_print(file, &(p->parm)); - p = p->next; - } - } else { // an update - assert(e->is_update()); - Alg_update_ptr u = (Alg_update_ptr) e; - if (u->get_identifier() != -1) { - file << " K" << u->get_identifier(); - } - parameter_print(file, &(u->parameter)); - } - file << "\n"; - } - } -} - -bool Alg_seq::write(const char *filename) -{ - ofstream file(filename); - if (file.fail()) return false; - write(file, units_are_seconds); - file.close(); - return true; -} diff --git a/plugins/MidiImport/portsmf/license.txt b/plugins/MidiImport/portsmf/license.txt deleted file mode 100644 index 6f86ce0d4fa..00000000000 --- a/plugins/MidiImport/portsmf/license.txt +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Portsmf: Portable Standard MIDI File Library - * - * license.txt -- a copy of the Portsmf copyright notice and license information - * - * Latest version available at: http://sourceforge.net/projects/portmedia - * - * Copyright (c) 1999-2000 Ross Bencina and Phil Burk - * Copyright (c) 2001-2006 Roger B. Dannenberg - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files - * (the "Software"), to deal in the Software without restriction, - * including without limitation the rights to use, copy, modify, merge, - * publish, distribute, sublicense, and/or sell copies of the Software, - * and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR - * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF - * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/* - * The text above constitutes the entire Portsmf license; however, - * the PortMusic community also makes the following non-binding requests: - * - * Any person wishing to distribute modifications to the Software is - * requested to send the modifications to the original developer so that - * they can be incorporated into the canonical version. It is also - * requested that these non-binding requests be included along with the - * license above. - */ diff --git a/plugins/MidiImport/portsmf/mfmidi.cpp b/plugins/MidiImport/portsmf/mfmidi.cpp deleted file mode 100644 index 52f93b83764..00000000000 --- a/plugins/MidiImport/portsmf/mfmidi.cpp +++ /dev/null @@ -1,485 +0,0 @@ -/* - * Read a Standard MIDI File. Externally-assigned function pointers are - * called upon recognizing things in the file. See midifile(3). - */ - -/***************************************************************************** -* Change Log -* Date | who : Change -*-----------+----------------------------------------------------------------- -* 2-Mar-92 | GWL : created changelog; MIDIFILE_ERROR to satisfy compiler -*****************************************************************************/ - -#include "stdio.h" -#include "mfmidi.h" -#include "string.h" - -#define MIDIFILE_ERROR -1 - -/* public stuff */ -extern int abort_flag; - - -void Midifile_reader::midifile() -{ - int ntrks; - midifile_error = 0; - - ntrks = readheader(); - if (midifile_error) return; - if (ntrks <= 0) { - mferror("No tracks!"); - /* no need to return since midifile_error is set */ - } - while (ntrks-- > 0 && !midifile_error) readtrack(); -} - -int Midifile_reader::readmt(const char *s, int skip) - /* read through the "MThd" or "MTrk" header string */ - /* if skip == 1, we attempt to skip initial garbage. */ -{ - int nread = 0; - char b[4]; - char buff[32]; - int c; - const char *errmsg = "expecting "; - - retry: - while ( nread<4 ) { - c = Mf_getc(); - if ( c == EOF ) { - errmsg = "EOF while expecting "; - goto err; - } - b[nread++] = c; - } - /* See if we found the 4 characters we're looking for */ - if ( s[0]==b[0] && s[1]==b[1] && s[2]==b[2] && s[3]==b[3] ) - return(0); - if ( skip ) { - /* If we are supposed to skip initial garbage, */ - /* try again with the next character. */ - b[0]=b[1]; - b[1]=b[2]; - b[2]=b[3]; - nread = 3; - goto retry; - } - err: - (void) strcpy(buff,errmsg); - (void) strcat(buff,s); - mferror(buff); - return(0); -} - -int Midifile_reader::egetc() - /* read a single character and abort on EOF */ -{ - int c = Mf_getc(); - - if ( c == EOF ) { - mferror("premature EOF"); - return EOF; - } - Mf_toberead--; - return(c); -} - -int Midifile_reader::readheader() - /* read a header chunk */ -{ - int format, ntrks, division; - - if ( readmt("MThd",Mf_skipinit) == EOF ) - return(0); - - Mf_toberead = read32bit(); - if (midifile_error) return MIDIFILE_ERROR; - format = read16bit(); - if (midifile_error) return MIDIFILE_ERROR; - ntrks = read16bit(); - if (midifile_error) return MIDIFILE_ERROR; - division = read16bit(); - if (midifile_error) return MIDIFILE_ERROR; - - Mf_header(format,ntrks,division); - - /* flush any extra stuff, in case the length of header is not 6 */ - while ( Mf_toberead > 0 && !midifile_error) - (void) egetc(); - return(ntrks); -} - -void Midifile_reader::readtrack() - /* read a track chunk */ -{ - /* This array is indexed by the high half of a status byte. It's */ - /* value is either the number of bytes needed (1 or 2) for a channel */ - /* message, or 0 (meaning it's not a channel message). */ - static int chantype[] = { - 0, 0, 0, 0, 0, 0, 0, 0, /* 0x00 through 0x70 */ - 2, 2, 2, 2, 1, 1, 2, 0 /* 0x80 through 0xf0 */ - }; - long lookfor, lng; - int c, c1, type; - int sysexcontinue = 0; /* 1 if last message was an unfinished sysex */ - int running = 0; /* 1 when running status used */ - int status = 0; /* (possibly running) status byte */ - int needed; - - if ( readmt("MTrk",0) == EOF ) - return; - - Mf_toberead = read32bit(); - - if (midifile_error) return; - - Mf_currtime = 0L; - - Mf_starttrack(); - - while ( Mf_toberead > 0 ) { - - Mf_currtime += readvarinum(); /* delta time */ - if (midifile_error) return; - - c = egetc(); - if (midifile_error) return; - - if ( sysexcontinue && c != 0xf7 ) { - mferror("didn't find expected continuation of a sysex"); - return; - } - if ( (c & 0x80) == 0 ) { /* running status? */ - if ( status == 0 ) { - mferror("unexpected running status"); - return; - } - running = 1; - } else { - status = c; - running = 0; - } - - needed = chantype[ (status>>4) & 0xf ]; - - if ( needed ) { /* ie. is it a channel message? */ - - if ( running ) - c1 = c; - else { - c1 = egetc(); - if (midifile_error) return; - } - chanmessage( status, c1, (needed>1) ? egetc() : 0 ); - if (midifile_error) return; - continue;; - } - - switch ( c ) { - - case 0xff: /* meta event */ - - type = egetc(); - if (midifile_error) return; - /* watch out - Don't combine the next 2 statements */ - lng = readvarinum(); - if (midifile_error) return; - lookfor = Mf_toberead - lng; - msginit(); - - while ( Mf_toberead > lookfor ) { - char c = egetc(); - if (midifile_error) return; - msgadd(c); - } - metaevent(type); - break; - - case 0xf0: /* start of system exclusive */ - - /* watch out - Don't combine the next 2 statements */ - lng = readvarinum(); - if (midifile_error) return; - lookfor = Mf_toberead - lng; - msginit(); - msgadd(0xf0); - - while ( Mf_toberead > lookfor ) { - c = egetc(); - if (midifile_error) return; - msgadd(c); - } - if ( c==0xf7 || Mf_nomerge==0 ) - sysex(); - else - sysexcontinue = 1; /* merge into next msg */ - break; - - case 0xf7: /* sysex continuation or arbitrary stuff */ - - /* watch out - Don't combine the next 2 statements */ - lng = readvarinum(); - if (midifile_error) return; - lookfor = Mf_toberead - lng; - - if ( ! sysexcontinue ) - msginit(); - - while ( Mf_toberead > lookfor ) { - c = egetc(); - if (midifile_error) return; - msgadd(c); - } - if ( ! sysexcontinue ) { - Mf_arbitrary(msgleng(), msg()); - } - else if ( c == 0xf7 ) { - sysex(); - sysexcontinue = 0; - } - break; - default: - - badbyte(c); - - break; - } - } - Mf_endtrack(); - return; -} - -void Midifile_reader::badbyte(int c) -{ - char buff[32]; - - (void) sprintf(buff,"unexpected byte: 0x%02x",c); - mferror(buff); -} - -void Midifile_reader::metaevent(int type) -{ - int leng = msgleng(); - char *m = msg(); - - switch ( type ) { - case 0x00: - Mf_seqnum(to16bit(m[0],m[1])); - break; - case 0x01: /* Text event */ - case 0x02: /* Copyright notice */ - case 0x03: /* Sequence/Track name */ - case 0x04: /* Instrument name */ - case 0x05: /* Lyric */ - case 0x06: /* Marker */ - case 0x07: /* Cue point */ - case 0x08: - case 0x09: - case 0x0a: - case 0x0b: - case 0x0c: - case 0x0d: - case 0x0e: - case 0x0f: - /* These are all text events */ - Mf_text(type,leng,m); - break; - case 0x20: - Mf_chanprefix(m[0]); - break; - case 0x21: - Mf_portprefix(m[0]); - break; - case 0x2f: /* End of Track */ - Mf_eot(); - break; - case 0x51: /* Set tempo */ - Mf_tempo(to32bit(0,m[0],m[1],m[2])); - break; - case 0x54: - Mf_smpte(m[0],m[1],m[2],m[3],m[4]); - break; - case 0x58: - Mf_timesig(m[0],m[1],m[2],m[3]); - break; - case 0x59: - Mf_keysig(m[0],m[1]); - break; - case 0x7f: - Mf_sqspecific(leng,m); - break; - default: - Mf_metamisc(type,leng,m); - } -} - - -void Midifile_reader::sysex() -{ - Mf_sysex(msgleng(), msg()); -} - - -void Midifile_reader::chanmessage(int status, int c1, int c2) -{ - int chan = status & 0xf; - - switch ( status & 0xf0 ) { - case NOTEOFF: - Mf_off(chan,c1,c2); - break; - case NOTEON: - Mf_on(chan,c1,c2); - break; - case PRESSURE: - Mf_pressure(chan,c1,c2); - break; - case CONTROLLER: - Mf_controller(chan,c1,c2); - break; - case PITCHBEND: - Mf_pitchbend(chan,c1,c2); - break; - case PROGRAM: - Mf_program(chan,c1); - break; - case CHANPRESSURE: - Mf_chanpressure(chan,c1); - break; - } -} - -/* readvarinum - read a varying-length number, and return the */ -/* number of characters it took. */ - -long Midifile_reader::readvarinum() -{ - long value; - int c; - - c = egetc(); - if (midifile_error) return 0; - - value = (long) c; - if ( c & 0x80 ) { - value &= 0x7f; - do { - c = egetc(); - if (midifile_error) return 0; - value = (value << 7) + (c & 0x7f); - } while (c & 0x80); - } - return (value); -} - -long Midifile_reader::to32bit(int c1, int c2, int c3, int c4) -{ - long value = 0L; - - value = (c1 & 0xff); - value = (value<<8) + (c2 & 0xff); - value = (value<<8) + (c3 & 0xff); - value = (value<<8) + (c4 & 0xff); - return (value); -} - -int Midifile_reader::to16bit(int c1, int c2) -{ - return ((c1 & 0xff ) << 8) + (c2 & 0xff); -} - -long Midifile_reader::read32bit() -{ - int c1, c2, c3, c4; - - c1 = egetc(); if (midifile_error) return 0; - c2 = egetc(); if (midifile_error) return 0; - c3 = egetc(); if (midifile_error) return 0; - c4 = egetc(); if (midifile_error) return 0; - return to32bit(c1,c2,c3,c4); -} - -int Midifile_reader::read16bit() -{ - int c1, c2; - c1 = egetc(); if (midifile_error) return 0; - c2 = egetc(); if (midifile_error) return 0; - return to16bit(c1,c2); -} - -void Midifile_reader::mferror(const char *s) -{ - Mf_error(s); - midifile_error = 1; -} - -/* The code below allows collection of a system exclusive message of */ -/* arbitrary length. The Msgbuff is expanded as necessary. The only */ -/* visible data/routines are msginit(), msgadd(), msg(), msgleng(). */ - -#define MSGINCREMENT 128 - -Midifile_reader::Midifile_reader() -{ - Mf_nomerge = 0; - Mf_currtime = 0L; - Mf_skipinit = 0; - Mf_toberead = 0; - - Msgbuff = 0; /* message buffer */ - Msgsize = 0; /* Size of currently allocated Msg */ - Msgindex = 0; /* index of next available location in Msg */ -} - -void Midifile_reader::finalize() -{ - if (Msgbuff) Mf_free(Msgbuff, Msgsize); - Msgbuff = NULL; -} - - -void Midifile_reader::msginit() -{ - Msgindex = 0; -} - -char *Midifile_reader::msg() -{ - return(Msgbuff); -} - -int Midifile_reader::msgleng() -{ - return(Msgindex); -} - -void Midifile_reader::msgadd(int c) -{ - /* If necessary, allocate larger message buffer. */ - if ( Msgindex >= Msgsize ) - msgenlarge(); - Msgbuff[Msgindex++] = c; -} - -void Midifile_reader::msgenlarge() -{ - char *newmess; - char *oldmess = Msgbuff; - int oldleng = Msgsize; - - Msgsize += MSGINCREMENT; - newmess = (char *) Mf_malloc((sizeof(char) * Msgsize) ); - - /* copy old message into larger new one */ - if ( oldmess != 0 ) { - register char *p = newmess; - register char *q = oldmess; - register char *endq = &oldmess[oldleng]; - - for ( ; q!=endq ; p++,q++ ) - *p = *q; - Mf_free(oldmess, oldleng); - } - Msgbuff = newmess; -} diff --git a/plugins/MidiImport/portsmf/mfmidi.h b/plugins/MidiImport/portsmf/mfmidi.h deleted file mode 100644 index d0049294bc2..00000000000 --- a/plugins/MidiImport/portsmf/mfmidi.h +++ /dev/null @@ -1,98 +0,0 @@ -#define NOTEOFF 0x80 -#define NOTEON 0x90 -#define PRESSURE 0xa0 -#define CONTROLLER 0xb0 -#define PITCHBEND 0xe0 -#define PROGRAM 0xc0 -#define CHANPRESSURE 0xd0 - -/* These are the strings used in keynote to identify Standard MIDI File */ -/* meta text messages. */ - -#define METATEXT "Text Event" -#define METACOPYRIGHT "Copyright Notice" -#define METASEQUENCE "Sequence/Track Name" -#define METAINSTRUMENT "Instrument Name" -#define METALYRIC "Lyric" -#define METAMARKER "Marker" -#define METACUE "Cue Point" -#define METAUNRECOGNIZED "Unrecognized" - - -class Midifile_reader { -public: - void midifile(); - int Mf_nomerge; /* 1 => continue'ed system exclusives are */ - /* not collapsed. */ - long Mf_currtime; /* current time in delta-time units */ - int Mf_skipinit; /* 1 if initial garbage should be skipped */ - Midifile_reader(); - // call finalize() when done or you may leak memory. - void finalize(); /* clean up before deletion */ - // Note: rather than finalize, we should have ~Midifile_reader(), - // but at least VC++ complains that there is no Mf_free(), even - // though Mf_free is declared as virtual and this is an abstract - // class. I don't understand this, so finalize() is a workaround. -RBD - -protected: - int midifile_error; - - virtual void *Mf_malloc(size_t size) = 0; /* malloc() */ - virtual void Mf_free(void *obj, size_t size) = 0; /* free() */ - /* Methods to be called while processing the MIDI file. */ - virtual void Mf_starttrack() = 0; - virtual void Mf_endtrack() = 0; - virtual int Mf_getc() = 0; - virtual void Mf_chanprefix(int) = 0; - virtual void Mf_portprefix(int) = 0; - virtual void Mf_eot() = 0; - virtual void Mf_error(const char *) = 0; - virtual void Mf_header(int,int,int) = 0; - virtual void Mf_on(int,int,int) = 0; - virtual void Mf_off(int,int,int) = 0; - virtual void Mf_pressure(int,int,int) = 0; - virtual void Mf_controller(int,int,int) = 0; - virtual void Mf_pitchbend(int,int,int) = 0; - virtual void Mf_program(int,int) = 0; - virtual void Mf_chanpressure(int,int) = 0; - virtual void Mf_sysex(int,char*) = 0; - virtual void Mf_arbitrary(int,char*) = 0; - virtual void Mf_metamisc(int,int,char*) = 0; - virtual void Mf_seqnum(int) = 0; - virtual void Mf_smpte(int,int,int,int,int) = 0; - virtual void Mf_timesig(int,int,int,int) = 0; - virtual void Mf_tempo(int) = 0; - virtual void Mf_keysig(int,int) = 0; - virtual void Mf_sqspecific(int,char*) = 0; - virtual void Mf_text(int,int,char*) = 0; - -private: - long Mf_toberead; - - long readvarinum(); - long read32bit(); - int read16bit(); - void msgenlarge(); - char *msg(); - int readheader(); - void readtrack(); - void sysex(); - void msginit(); - int egetc(); - int msgleng(); - - int readmt(const char *, int); - long to32bit(int,int,int,int); - int to16bit(int,int); - void mferror(const char *); - void badbyte(int); - void metaevent(int); - void msgadd(int); - void chanmessage(int,int,int); - - char *Msgbuff; - long Msgsize; - long Msgindex; -}; - - diff --git a/plugins/MidiImport/portsmf/strparse.cpp b/plugins/MidiImport/portsmf/strparse.cpp deleted file mode 100644 index 7665b4ae058..00000000000 --- a/plugins/MidiImport/portsmf/strparse.cpp +++ /dev/null @@ -1,87 +0,0 @@ -#include -#include -// #include -- for debugging (cout) -#include "ctype.h" -using namespace std; -#include "strparse.h" - -void String_parse::skip_space() -{ - while ((*str)[pos] && isspace((*str)[pos])) { - pos = pos + 1; - } -} - - -char String_parse::peek() -{ - return (*str)[pos]; -} - - -void String_parse::get_nonspace_quoted(string &field) -{ - field.clear(); - skip_space(); - bool quoted = false; - if ((*str)[pos] == '"') { - quoted = true; - field.append(1, '"'); - pos = pos + 1; - } - while ((*str)[pos] && (quoted || !isspace((*str)[pos]))) { - if ((*str)[pos] == '"') { - if (quoted) { - field.append(1, '"'); - pos = pos + 1; - } - return; - } - if ((*str)[pos] == '\\') { - pos = pos + 1; - } - if ((*str)[pos]) { - field.append(1, (*str)[pos]); - pos = pos + 1; - } - } -} - - -char *escape_chars[] = { (char *) "\\n", (char *)"\\t", (char *)"\\\\", (char *)"\\r", (char *) "\\\""}; - - -void string_escape(string &result, char *str, const char *quote) -{ - int length = (int) strlen(str); - if (quote[0]) { - result.append(1, quote[0]); - } - for (int i = 0; i < length; i++) { - if (!isalnum((unsigned char) str[i])) { - char *chars = (char *)"\n\t\\\r\""; - char *special = strchr(chars, str[i]); - if (special) { - result.append(escape_chars[special - chars]); - } else { - result.append(1, str[i]); - } - } else { - result.append(1, str[i]); - } - } - result.append(1, quote[0]); -} - -void String_parse::get_remainder(std::string &field) -{ - field.clear(); - skip_space(); - int len = str->length() - pos; - if ((*str)[len - 1] == '\n') { // if str ends in newline, - len--; // reduce length to ignore newline - } - field.insert(0, *str, pos, len); -} - - diff --git a/plugins/MidiImport/portsmf/strparse.h b/plugins/MidiImport/portsmf/strparse.h deleted file mode 100644 index 74f01591974..00000000000 --- a/plugins/MidiImport/portsmf/strparse.h +++ /dev/null @@ -1,18 +0,0 @@ -// strparse.h -- header for String_parse class - -class String_parse { -public: - int pos; - std::string *str; - void init(std::string *s) { - str = s; - pos = 0; - } - void skip_space(); - char peek(); - void get_nonspace_quoted(std::string &field); - // get the remaining characters, skipping initial spaces and final return - void get_remainder(std::string &field); -}; - -void string_escape(std::string &result, char *s, const char *quote); diff --git a/plugins/MidiImport/portsmf/trace.cpp b/plugins/MidiImport/portsmf/trace.cpp deleted file mode 100644 index 7c1999db570..00000000000 --- a/plugins/MidiImport/portsmf/trace.cpp +++ /dev/null @@ -1,25 +0,0 @@ -// trace.cpp -- debugging print function -// -// (I think this was created to provide a generic print function -// for use in non-command-line Windows applications where printf -// does not work. Currently, it is not used, but kept around for -// possible debugging needs. -RBD) - -#include "stdarg.h" -#include "stdio.h" -#include "crtdbg.h" - - -void trace(char *format, ...) -{ - char msg[256]; - va_list args; - va_start(args, format); - _vsnprintf(msg, 256, format, args); - va_end(args); -#ifdef _DEBUG - _CrtDbgReport(_CRT_WARN, NULL, NULL, NULL, msg); -#else - printf(msg); -#endif -} diff --git a/plugins/MidiImport/portsmf/trace.h b/plugins/MidiImport/portsmf/trace.h deleted file mode 100644 index 5726f0c5de6..00000000000 --- a/plugins/MidiImport/portsmf/trace.h +++ /dev/null @@ -1,2 +0,0 @@ -void trace(char *format, ...); - diff --git a/plugins/SmfImport/CMakeLists.txt b/plugins/SmfImport/CMakeLists.txt new file mode 100644 index 00000000000..d5a15c1f79c --- /dev/null +++ b/plugins/SmfImport/CMakeLists.txt @@ -0,0 +1,36 @@ +# Importer for SMF-like file format, such as .mid, .ove, .wrk + +# check for drumstick-file +IF(WANT_QT5) + PKG_CHECK_MODULES(DRUMSTICK_FILE REQUIRED drumstick-file>=1.0.0) +ELSE() + PKG_CHECK_MODULES(DRUMSTICK_FILE REQUIRED drumstick-file>=0.5.0) +ENDIF() + +IF(NOT DRUMSTICK_FILE_FOUND) + IF(WANT_QT5) + MESSAGE(FATAL_ERROR "LMMS requires libdrumstick-file and libdrumstick-dev >= 1.0.0 with Qt5 - please install, remove CMakeCache.txt and try again!") + ELSE() + MESSAGE(FATAL_ERROR "LMMS requires libdrumstick-file and libdrumstick-dev >= 0.5.0 with Qt4 - please install, remove CMakeCache.txt and try again!") + ENDIF() +ENDIF(NOT DRUMSTICK_FILE_FOUND) + +INCLUDE(BuildPlugin) + +LINK_DIRECTORIES(${DRUMSTICK_FILE_LIBDIR}) +INCLUDE_DIRECTORIES(${DRUMSTICK_FILE_INCLUDEDIR}) + +SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fexceptions") + +BUILD_PLUGIN(smfimport SmfImport.cpp commonReader.cpp midiReader.cpp oveReader.cpp wrkReader.cpp + MOCFILES SmfImport.h commonReader.h midiReader.h oveReader.h wrkReader.h ) + +IF(NOT LMMS_BUILD_LINUX) + IF(WANT_QT5) + TARGET_LINK_LIBRARIES(smfimport ${DRUMSTICK_FILE_LIBRARIES} Qt5::Core) + ELSE() + TARGET_LINK_LIBRARIES(smfimport ${DRUMSTICK_FILE_LIBRARIES} -lQtCore4) + ENDIF() +ELSE() + TARGET_LINK_LIBRARIES(smfimport ${DRUMSTICK_FILE_LIBRARIES}) +ENDIF() diff --git a/plugins/SmfImport/SmfImport.cpp b/plugins/SmfImport/SmfImport.cpp new file mode 100644 index 00000000000..9b83e9008de --- /dev/null +++ b/plugins/SmfImport/SmfImport.cpp @@ -0,0 +1,234 @@ +/* + * SmfImport.h - support for importing SMF-liked files + * + * Copyright (c) 2016-2017 Tony Chyi + * + * This file is part of LMMS - http://lmms.io + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program (see COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + */ + +#include +#include +#include +#include +#include + +#include "TrackContainer.h" +#include "InstrumentTrack.h" +#include "AutomationTrack.h" +#include "AutomationPattern.h" +#include "ConfigManager.h" +#include "Pattern.h" +#include "Instrument.h" +#include "GuiApplication.h" +#include "MainWindow.h" +#include "MidiTime.h" +#include "debug.h" +#include "embed.h" +#include "Song.h" + +#include "SmfImport.h" +#include "midiReader.h" +#include "oveReader.h" +#include "wrkReader.h" + +extern "C" +{ + + Plugin::Descriptor PLUGIN_EXPORT smfimport_plugin_descriptor = + { + STRINGIFY( PLUGIN_NAME ), + "SMF Import", + QT_TRANSLATE_NOOP( "pluginBrowser", + "Filter for importing MIDI-like files into LMMS" ), + "Tony Chyi ", + 0x0100, + Plugin::ImportFilter, + NULL, + NULL, + NULL + } ; + +} + + + +SmfImport::SmfImport( const QString &_file ): + ImportFilter( _file, &smfimport_plugin_descriptor ) +{ + +} + +SmfImport::~SmfImport() +{ + closeFile(); +} + +bool SmfImport::tryImport( TrackContainer *tc ) +{ + +#ifdef LMMS_HAVE_FLUIDSYNTH + + if( gui != NULL && ConfigManager::inst()->defaultSoundfont().isEmpty() ) + { + QMessageBox::information( gui->mainWindow(), + tr( "Setup incomplete" ), + tr( "You do not have set up a default soundfont in " + "the settings dialog (Edit->Settings). " + "Therefore no sound will be played back after " + "importing this MIDI file. You should download " + "a General MIDI soundfont, specify it in " + "settings dialog and try again." ) ); + } + +#else + + if( gui ) + { + QMessageBox::information( gui->mainWindow(), + tr( "Setup incomplete" ), + tr( "You did not compile LMMS with support for " + "SoundFont2 player, which is used to add default " + "sound to imported MIDI files. " + "Therefore no sound will be played back after " + "importing this MIDI file." ) ); + } + + return false; +#endif + + if( !openFile() ) + { + return false; + } + + filename = file().fileName(); + + switch( readID() ) + { + case makeID( 'M', 'T', 'h', 'd' ): + printf( "SmfImport::tryImport(): found MThd\n" ); + return readSMF( tc ); + + case makeID( 'R', 'I', 'F', 'F' ): + printf( "SmfImport::tryImport(): found RIFF\n" ); + return readRIFF( tc ); + + case makeID( 'O', 'V', 'S', 'C' ): + printf( "SmfImport::tryImport(): found OVSC\n" ); + return readOve( tc ); + + case makeID( 'C', 'A', 'K', 'E' ): + printf( "SmfImport::tryImport(): found CAKEWALK\n" ); + return readWrk( tc ); + + default: + printf( "MidiImport::tryImport(): not a Standard MIDI " + "file\n" ); + return false; + } + +} + +bool SmfImport::readWrk( TrackContainer *tc ) +{ + wrkReader mr( tc ); + mr.read( filename ); + return true; +} + +bool SmfImport::readOve( TrackContainer *tc ) +{ + oveReader mr( tc ); + mr.read( filename ); + return true; +} + +bool SmfImport::readSMF( TrackContainer *tc ) +{ + midiReader mr( tc ); + mr.read( filename ); + return true; +} + +bool SmfImport::readRIFF( TrackContainer* tc ) +{ + // skip file length + skip( 4 ); + + // check file type ("RMID" = RIFF MIDI) + if( readID() != makeID( 'R', 'M', 'I', 'D' ) ) + { +invalid_format: + qWarning( "MidiImport::readRIFF(): invalid file format" ); + return false; + } + + // search for "data" chunk + while( 1 ) + { + const int id = readID(); + const int len = read32LE(); + + if( file().atEnd() ) + { +data_not_found: + qWarning( "MidiImport::readRIFF(): data chunk not found" ); + return false; + } + + if( id == makeID( 'd', 'a', 't', 'a' ) ) + { + break; + } + + if( len < 0 ) + { + goto data_not_found; + } + + skip( ( len + 1 ) & ~1 ); + } + + // the "data" chunk must contain data in SMF format + if( readID() != makeID( 'M', 'T', 'h', 'd' ) ) + { + goto invalid_format; + } + + return readSMF( tc ); +} + +void SmfImport::error() +{ + +} + + +extern "C" +{ + +// necessary for getting instance out of shared lib + Plugin * PLUGIN_EXPORT lmms_plugin_main( Model *, void * _data ) + { + return new SmfImport( QString::fromUtf8( + static_cast( _data ) ) ); + } + + +} diff --git a/plugins/MidiImport/MidiImport.h b/plugins/SmfImport/SmfImport.h similarity index 76% rename from plugins/MidiImport/MidiImport.h rename to plugins/SmfImport/SmfImport.h index d4c7a0f4a58..9e0145f5abf 100644 --- a/plugins/MidiImport/MidiImport.h +++ b/plugins/SmfImport/SmfImport.h @@ -1,7 +1,7 @@ /* - * MidiImport.h - support for importing MIDI-files + * SmfImport.cpp - support for importing SMF-liked files * - * Copyright (c) 2005-2014 Tobias Doerffel + * Copyright (c) 2016-2017 Tony Chyi * * This file is part of LMMS - http://lmms.io * @@ -22,52 +22,56 @@ * */ -#ifndef _MIDI_IMPORT_H -#define _MIDI_IMPORT_H +#ifndef SMF_IMPORT_H +#define SMF_IMPORT_H #include #include #include +#include #include "MidiEvent.h" #include "ImportFilter.h" - -class MidiImport : public ImportFilter +class SmfImport : public ImportFilter { Q_OBJECT public: - MidiImport( const QString & _file ); - virtual ~MidiImport(); + SmfImport( const QString & _file ); + virtual ~SmfImport(); virtual PluginView * instantiateView( QWidget * ) { - return( NULL ); + return NULL; } - private: - virtual bool tryImport( TrackContainer* tc ); + virtual bool tryImport( TrackContainer *tc ); bool readSMF( TrackContainer* tc ); bool readRIFF( TrackContainer* tc ); - bool readTrack( int _track_end, QString & _track_name ); - - void error( void ); + bool readOve( TrackContainer* tc ); + bool readWrk( TrackContainer* tc ); + void error(); inline int readInt( int _bytes ) { int c, value = 0; + do { c = readByte(); + if( c == -1 ) { return( -1 ); } + value = ( value << 8 ) | c; - } while( --_bytes ); + } + while( --_bytes ); + return( value ); } inline int read32LE() @@ -82,26 +86,31 @@ class MidiImport : public ImportFilter { int c = readByte(); int value = c & 0x7f; + if( c & 0x80 ) { c = readByte(); value = ( value << 7 ) | ( c & 0x7f ); + if( c & 0x80 ) { c = readByte(); value = ( value << 7 ) | ( c & 0x7f ); + if( c & 0x80 ) { c = readByte(); value = ( value << 7 ) | c; + if( c & 0x80 ) { return -1; } } } - } - return( !file().atEnd() ? value : -1 ); + } + + return( !file().atEnd() ? value : -1 ); } inline int readID() @@ -117,12 +126,7 @@ class MidiImport : public ImportFilter } } - - typedef QVector > EventVector; - EventVector m_events; - int m_timingDivision; - -} ; - + QString filename; +}; #endif diff --git a/plugins/SmfImport/commonReader.cpp b/plugins/SmfImport/commonReader.cpp new file mode 100644 index 00000000000..63646c1c8f8 --- /dev/null +++ b/plugins/SmfImport/commonReader.cpp @@ -0,0 +1,405 @@ +/* + * commonReader.cpp - import backend. + * + * Copyright (c) 2016-2017 Tony Chyi + * + * This file is part of LMMS - http://lmms.io + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program (see COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + */ + +#include +#include +#include +#include +#include + +#include "GuiApplication.h" +#include "TrackContainer.h" +#include "Engine.h" +#include "Song.h" +#include "AutomationPattern.h" +#include "MidiTime.h" +#include "MainWindow.h" + +#include "SmfMidiCC.h" +#include "SmfMidiChannel.h" + +#include "commonReader.h" + +#define CHECK_TRACK track = ( track == noTrack ? chan : track ); +#define PITCH_RANGE_RPN_CODE {track, 0} +#define CC_RPN_SEND rpn_data[0] = track; \ + rpn_data[1] = value; + +#define NOTE_EVENT_DEFINITION const int time = 0; \ + const int channel = time + 1; \ + const int note_pitch = channel + 1; \ + const int note_vol = note_pitch + 1; + +commonReader::commonReader( TrackContainer *tc, const QString hintText ): + m_tc( tc ), + beatsPerTact( 4 ), + pitchBendMultiply( defaultPitchRange ), + pd( hintText, TrackContainer::tr( "Cancel" ), + 0, preTrackSteps, gui->mainWindow() ) +{ + m_currentTrackName.first = -1; + pd.setWindowTitle( TrackContainer::tr( "Please wait..." ) ); + pd.setWindowModality( Qt::WindowModal ); + pd.setMinimumDuration( 0 ); + pd.setValue( 0 ); + + + MeterModel & timeSigMM = Engine::getSong()->getTimeSigModel(); + timeSigNumeratorPat = AutomationPattern::globalAutomationPattern( + &timeSigMM.numeratorModel() ); + timeSigDenominatorPat = AutomationPattern::globalAutomationPattern( + &timeSigMM.denominatorModel() ); + + ticksPerBeat = DefaultTicksPerTact / beatsPerTact; + + if( note_list.size() ) + note_list.clear(); + + if( rpn_msbs.size() ) + rpn_msbs.clear(); + + if( rpn_lsbs.size() ) + rpn_lsbs.clear(); + + AutomationPattern * tap = m_tc->tempoAutomationPattern(); + tap->clear(); +} + +commonReader::~commonReader() +{ + printf( "destory fileReader\n" ); + + for( int c = 0; c < 256; c++ ) + { + if( !chs[c].hasNotes && chs[c].it ) + { + printf( " Should remove empty track\n" ); + // must delete trackView first - but where is it? + //m_tc->removeTrack( chs[c].it ); + //chs[c].it->deleteLater(); + } + } + +} + +void commonReader::CCHandler( long tick, int track, int ctl, int value ) +{ + QString trackName; + + if( m_currentTrackName.first == m_currentTrack ) + { + trackName = m_currentTrackName.second; + } + else + { + trackName = TrackContainer::tr( "Track %1" ).arg( track + 1 ); + } + + SmfMidiChannel * ch = chs[track].create( m_tc, trackName ); + AutomatableModel * objModel = NULL; + int * rpn_data = new int[2]; + + bool flag_rpn_msb = false; + bool flag_rpn_lsb = false; + + if( ctl <= 129 ) + { + switch( ctl ) + { + case bankEventId: + if( ch->isSF2 && ch->it_inst ) + { + if( track != 9 ) + { + objModel = ch->it_inst->childModel( "bank" ); + printf( "Tick=%ld: BANK SELECT %d\n", tick, value ); + } + } + + break; + + case 6: + for( int c = 0; c < rpn_msbs.size(); c++ ) + { + rpn_data = rpn_msbs[c]; + + if( rpn_data[0] == track && rpn_data[1] == 0 ) + { + flag_rpn_msb = true; + rpn_msbs.removeAt( c ); + delete rpn_data; + } + } + + for( int c = 0; c < rpn_lsbs.size(); c++ ) + { + rpn_data = rpn_lsbs[c]; + + if( rpn_data[0] == track && rpn_data[1] == 0 ) + { + flag_rpn_lsb = true; + rpn_lsbs.removeAt( c ); + delete rpn_data; + } + } + + if( flag_rpn_lsb && flag_rpn_msb ) + { + objModel = ch->it->pitchRangeModel(); + pitchBendMultiply = value; + } + + break; + + case volumeEventId: + objModel = ch->it->volumeModel(); + value = value * 100 / 127; + break; + + case panEventId: + objModel = ch->it->panningModel(); + // value may be nagetive. + value = value * 100 / 127; + break; + + // RPN LSB + case 100: + CC_RPN_SEND + rpn_lsbs << rpn_data; + break; + + // RPN MSB + case 101: + CC_RPN_SEND + rpn_msbs << rpn_data; + break; + + case pitchBendEventId: + objModel = ch->it->pitchModel(); + value = value * 100 / 8192 * pitchBendMultiply; + break; + + case programEventId: + if( ch->isSF2 && ch->it_inst ) + objModel = ch->it_inst->childModel( "patch" ); + + break; + + default: + // TODO: something useful for other CCs + printf( "Tick=%ld: Unused CC %d with value=%d\n", + tick, ctl, value ); + break; + } + + if( objModel ) + { + if( tick == 0 ) + objModel->setInitValue( value ); + else + { + if( ccs[track][ctl].at == NULL ) + { + ccs[track][ctl].create( m_tc, trackName + " > " + objModel->displayName() ); + } + + ccs[track][ctl].putValue( tick * tickRate , objModel, value ); + } + } + } + +} + +void commonReader::programHandler( long tick, int chan, int patch, + int track ) +{ + CHECK_TRACK + QString trackName = TrackContainer::tr( "Track %1" ).arg( chan + 1 ); + SmfMidiChannel * ch = chs[track].create( m_tc, trackName ); + + if( ch->isSF2 ) + { + // AFAIK, 128 should be the standard bank for drums in SF2. + // If not, this has to be made configurable. + ch->it_inst->childModel( "bank" )->setValue( chan != 9 ? 0 : 128 ); + + if( tick == 0 ) + ch->it_inst->childModel( "patch" )->setValue( patch ); + else + CCHandler( tick, track, programEventId, patch ); + } + else + { + const QString num = QString::number( patch ); + const QString filter = QString().fill( '0', 3 - num.length() ) + num + "*.pat"; + const QString dir = "/usr/share/midi/" + "freepats/Tone_000/"; + const QStringList files = QDir( dir ). + entryList( QStringList( filter ) ); + + if( ch->it_inst && !files.empty() ) + { + ch->it_inst->loadFile( dir + files.front() ); + } + } + +} + +void commonReader::timeSigHandler( long tick, int num, int den ) +{ + printf( "Another timesig at %f\n", tick * tickRate ); + timeSigNumeratorPat->putValue( tick * tickRate, num ); + timeSigDenominatorPat->putValue( tick * tickRate, den ); + pd.setValue( preTrackSteps ); +} + +void commonReader::tempoHandler( long tick, int tempo ) +{ + AutomationPattern * tap = m_tc->tempoAutomationPattern(); + + if( tap ) + { + tap->putValue( static_cast( tick ) * tickRate, tempo ); + } +} + +void commonReader::textHandler( int text_type, const QString &data , int track ) +{ + switch ( text_type ) + { + case 3: + + // Track Name + m_currentTrack = ( track == -10 ? m_currentTrack : track ); + + if ( m_currentTrack == noTrack ) // Meta name, i.e. The real title/name of the current song file + { + return; // Don't know how to handle this in LMMS + } + + m_currentTrackName.first = m_currentTrack; + m_currentTrackName.second = data; + + if( chs[m_currentTrack].it ) + { + chs[m_currentTrack].setName( data ); + } + else + { + chs[m_currentTrack].create( m_tc, data ); + } + + case 5: + + // Lyrics + // How to handle lyrics? + case 6: + + // MIDI Marker Description + // How to handle markers & cue points? + + default: + printf( "Unknown Text Event: %d %s\n", text_type, data.toStdString().c_str() ); + } + +} + +void commonReader::timeBaseHandler( int timebase ) +{ + tickRate = ticksPerBeat / static_cast( timebase ); +} + +void commonReader::trackStartHandler() +{ + m_currentTrack ++; +} + +// when use this, addNoteEvent() should be called later, otherwise it will cause memeory leak. +void commonReader::insertNoteEvent( long tick, int chan, int pitch, + int vol, int track ) +{ + NOTE_EVENT_DEFINITION + CHECK_TRACK + + int *note = new int[4]; + note[time] = tick; + note[channel] = track; + note[note_pitch] = pitch; + note[note_vol] = vol; + note_list << note; +} + +void commonReader::addNoteEvent(long tick, int chan, + int pitch, int vol, int track ) +{ + NOTE_EVENT_DEFINITION + CHECK_TRACK + + if( !note_list.size() && vol ) + { + addNoteEvent( tick, chan, pitch, vol, 1, track ); + } + + for( int c = 0; c < note_list.size(); c++ ) + { + int *note; + note = note_list[c]; + + if( note[channel] == track && note[note_pitch] == pitch + && tick >= note[time] ) + { + int dur = tick - note[time]; + addNoteEvent( note[time], chan, pitch, note[note_vol], dur, track ); + note_list.removeAt( c ); + delete note; + break; + } + } + + if( pd.maximum() <= chan + preTrackSteps ) + pd.setMaximum( pd.maximum() + preTrackSteps ); + + if( pd.value() <= chan + preTrackSteps ) + pd.setValue( chan + preTrackSteps ); + +} + +void commonReader::addNoteEvent( long tick, int chan, int pitch, int vol, + int dur, int track ) +{ + CHECK_TRACK + + QString trackName = TrackContainer::tr( "Track %1" ).arg( track + 1 ); + SmfMidiChannel * ch = chs[track].create( m_tc, trackName ); + double realDur = dur * tickRate; + Note n( ( realDur < 1 ? 1 : realDur ), static_cast( tick ) * tickRate, pitch - 12, vol * 200 / 127 ); + ch->addNote( n ); + +} + +void commonReader::errorHandler( const QString &errorStr ) +{ + printf( "SmfImport::readFile(): got error %s\n", + errorStr.toStdString().c_str() ); +} diff --git a/plugins/SmfImport/commonReader.h b/plugins/SmfImport/commonReader.h new file mode 100644 index 00000000000..ba0fd39c445 --- /dev/null +++ b/plugins/SmfImport/commonReader.h @@ -0,0 +1,112 @@ +/* + * commonReader.h - import backend. + * + * Copyright (c) 2016-2017 Tony Chyi + * + * This file is part of LMMS - http://lmms.io + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program (see COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + */ + +#ifndef COMMON_READER_H +#define COMMON_READER_H + +#include +#include +#include + +#include "TrackContainer.h" +#include "MeterModel.h" +#include "AutomationPattern.h" + +#include "SmfMidiCC.h" +#include "SmfMidiChannel.h" + +#define makeID(_c0, _c1, _c2, _c3) \ + ( 0 | \ + ( ( _c0 ) | ( ( _c1 ) << 8 ) | ( ( _c2 ) << 16 ) | ( ( _c3 ) << 24 ) ) ) + +const int defaultPitchRange = 2; +const int preTrackSteps = 2; + +const int bankEventId = 0; +const int volumeEventId = 7; +const int panEventId = 10; +const int pitchBendEventId = 128; +const int programEventId = 129; + +const int noTrack = -1; + +class commonReader : public QObject +{ + Q_OBJECT +public: + commonReader( TrackContainer *tc, const QString hintText ); + ~commonReader(); +protected: + virtual void errorHandler( const QString &errorStr ); + void CCHandler( long tick, int track, int ctl, int value ); + void programHandler( long tick, int chan, int patch, int track = noTrack ); + void timeSigHandler( long tick, int num, int den ); + void tempoHandler( long tick, int tempo ); + void textHandler( int text_type, const QString& data , int track = -10 ); + void timeBaseHandler( int timebase ); + void trackStartHandler(); + + void insertNoteEvent( long tick, int chan, int pitch, int vol, int track = noTrack ); + + void addNoteEvent( long tick, int chan, int pitch, int vol, int track = noTrack ); + void addNoteEvent( long tick, int chan, int pitch, int vol, int dur, int track ); + + + TrackContainer *m_tc; + + // 128 CC + Pitch Bend + Program + SmfMidiCC ccs[256][130]; + SmfMidiChannel chs[256]; + + AutomationPattern * timeSigNumeratorPat; + AutomationPattern * timeSigDenominatorPat; + + double beatsPerTact; + double ticksPerBeat; + double tickRate; // convert ove tick to lmms tick. + + int pitchBendMultiply; + int m_currentTrack = -2; + QPair m_currentTrackName; + + QProgressDialog pd; + + /* + * record note event. + * tick, channel, pitch, vol. + */ + QList note_list; + + /* + * record rpn event. + * chan, value + */ + QList rpn_msbs; + QList rpn_lsbs; + + +}; + +#endif + diff --git a/plugins/SmfImport/midiReader.cpp b/plugins/SmfImport/midiReader.cpp new file mode 100644 index 00000000000..cdf10556596 --- /dev/null +++ b/plugins/SmfImport/midiReader.cpp @@ -0,0 +1,166 @@ +/* + * midiReader.cpp - support for importing MIDI files + * + * Copyright (c) 2005-2014 Tobias Doerffel + * + * This file is part of LMMS - http://lmms.io + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program (see COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + */ + +#include +#include +#include +#include +#include + +#include "GuiApplication.h" +#include "TrackContainer.h" +#include "Engine.h" +#include "Song.h" +#include "AutomationPattern.h" +#include "MidiTime.h" +#include "MainWindow.h" + +#include "SmfMidiCC.h" +#include "SmfMidiChannel.h" + +#include "midiReader.h" + +midiReader::midiReader( TrackContainer* tc ) : + commonReader( tc, commonReader::tr( "Importing %1 file..." ).arg( "MIDI" ) ), + m_seq( new drumstick::QSmf ) +{ + // Connect to slots. + connect( m_seq, SIGNAL( signalSMFTimeSig( int, int, int, int ) ), + this, SLOT( timeSigEvent( int, int, int, int ) ) ); + + connect( m_seq, SIGNAL( signalSMFTempo( int ) ), + this, SLOT( tempoEvent( int ) ) ); + + connect( m_seq, SIGNAL( signalSMFText( int, QString ) ), + this, SLOT( textEvent( int, QString ) ) ); + + connect( m_seq, SIGNAL( signalSMFError( QString ) ), + this, SLOT( errorHandler( QString ) ) ); + + connect( m_seq, SIGNAL( signalSMFCtlChange( int, int, int ) ), + this, SLOT( ctlChangeEvent( int, int, int ) ) ); + + connect( m_seq, SIGNAL( signalSMFPitchBend( int, int ) ), + this, SLOT( pitchBendEvent( int, int ) ) ); + + connect( m_seq, SIGNAL( signalSMFNoteOn( int, int, int ) ), + this, SLOT( noteOnEvent( int, int, int ) ) ); + + connect( m_seq, SIGNAL( signalSMFNoteOff( int, int, int ) ), + this, SLOT( noteOffEvent( int, int, int ) ) ); + + connect( m_seq, SIGNAL( signalSMFProgram( int, int ) ), + this, SLOT( programEvent( int, int ) ) ); + + connect( m_seq, SIGNAL( signalSMFHeader( int, int, int ) ), + this, SLOT( headerEvent( int, int, int ) ) ); + + connect( m_seq, SIGNAL( signalSMFTrackStart() ), this, + SLOT( startTrackEvent() ) ); + +} + +midiReader::~midiReader() +{ + printf( "destroy midiReader\n" ); + delete m_seq; +} + +void midiReader::read( QString &fileName ) +{ + m_seq->readFromFile( fileName ); +} + +// Slots below. + +// b0: Numerator +// b1: Denominator (exponent in a power of two) +// b2: Number of MIDI clocks per metronome click +// b3: Number of notated 32nd notes per 24 MIDI clocks +void midiReader::timeSigEvent( int b0, int b1, int b2, int b3 ) +{ + timeSigHandler( m_seq->getCurrentTime(), b0, 1 << b1 ); +} + +void midiReader::tempoEvent( int tempo ) +{ + tempoHandler( m_seq->getCurrentTime(), 60000000 / tempo ); +} + +void midiReader::textEvent( int text_type, const QString &data ) +{ + if ( data.length() ) + { + textHandler( text_type, data ); + } +} + +void midiReader::startTrackEvent() +{ + trackStartHandler(); +} + +void midiReader::errorHandler( const QString &errorStr ) +{ + printf( "MidiImport::readSMF(): got error %s at %ld\n", + errorStr.toStdString().c_str(), m_seq->getCurrentTime() ); +} + +void midiReader::ctlChangeEvent( int chan, int ctl, int value ) +{ + CCHandler( m_seq->getCurrentTime(), chan, ctl, value ); +} + +void midiReader::pitchBendEvent( int chan, int value ) +{ + CCHandler( m_seq->getCurrentTime(), chan, pitchBendEventId, value ); +} + +void midiReader::noteOnEvent( int chan, int pitch, int vol ) +{ + if( vol ) + { + insertNoteEvent( m_seq->getCurrentTime(), chan, pitch, vol ); + } + else + { + addNoteEvent( m_seq->getCurrentTime(), chan, pitch, vol ); + } +} + +void midiReader::noteOffEvent( int chan, int pitch, int vol ) +{ + addNoteEvent( m_seq->getCurrentTime(), chan, pitch, vol ); +} + +void midiReader::headerEvent( int format, int ntrks, int division ) +{ + timeBaseHandler( division ); + pd.setMaximum( ntrks + preTrackSteps ); +} + +void midiReader::programEvent( int chan, int patch ) +{ + programHandler( m_seq->getCurrentTime(), chan, patch ); +} diff --git a/plugins/SmfImport/midiReader.h b/plugins/SmfImport/midiReader.h new file mode 100644 index 00000000000..f153f31ab21 --- /dev/null +++ b/plugins/SmfImport/midiReader.h @@ -0,0 +1,54 @@ +/* + * midiReader.h - support for importing MIDI files + * + * Copyright (c) 2005-2014 Tobias Doerffel + * + * This file is part of LMMS - http://lmms.io + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program (see COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + */ + +#ifndef MIDI_READER_H +#define MIDI_READER_H + +#include + +#include "commonReader.h" +class midiReader : public commonReader +{ + Q_OBJECT +public: + midiReader( TrackContainer *tc ); + ~midiReader(); + void read( QString &fileName ); +public slots: + void headerEvent( int format, int ntrks, int division ); + void noteOnEvent( int chan, int pitch, int vol ); + void noteOffEvent( int chan, int pitch, int vol ); + void ctlChangeEvent( int chan, int ctl, int value ); + void pitchBendEvent( int chan, int value ); + void programEvent( int chan, int patch ); + void timeSigEvent( int b0, int b1, int b2, int b3 ); + void tempoEvent( int tempo ); + void textEvent( int text_type, const QString& data ); + void startTrackEvent(); + void errorHandler( const QString& errorStr ); +private: + drumstick::QSmf *m_seq; +}; + +#endif diff --git a/plugins/SmfImport/oveReader.cpp b/plugins/SmfImport/oveReader.cpp new file mode 100644 index 00000000000..8f5186684a4 --- /dev/null +++ b/plugins/SmfImport/oveReader.cpp @@ -0,0 +1,176 @@ +/* + * oveReader.cpp - support for importing Overture files + * + * Copyright (c) 2016-2017 Tony Chyi + * + * This file is part of LMMS - http://lmms.io + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program (see COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + */ + +#include +#include +#include +#include +#include + +#include "GuiApplication.h" +#include "TrackContainer.h" +#include "Engine.h" +#include "Song.h" +#include "AutomationPattern.h" +#include "MidiTime.h" +#include "MainWindow.h" + +#include "SmfMidiCC.h" +#include "SmfMidiChannel.h" + +#include "oveReader.h" + +oveReader::oveReader( TrackContainer* tc ) : + commonReader( tc, commonReader::tr( "Importing %1 file..." ).arg( "Overture" ) ), + m_seq( new drumstick::QOve ) +{ + // Connect to slots. + connect( m_seq, SIGNAL( signalOVEError( QString ) ), + this, SLOT( errorHandler( QString ) ) ); + + connect( m_seq, SIGNAL( signalOVEHeader( int, int ) ), + this, SLOT( fileHeader( int, int ) ) ); + + connect( m_seq, SIGNAL( signalOVENoteOn( int, long, int, int, int ) ), + this, SLOT( noteOnEvent( int, long, int, int, int ) ) ); + + connect( m_seq, SIGNAL( signalOVENoteOff( int, long, int, int, int ) ), + this, SLOT( noteOffEvent( int, long, int, int, int ) ) ); + + connect( m_seq, SIGNAL( signalOVECtlChange( int, long, int, int, int ) ), + this, SLOT( ctlChangeEvent( int, long, int, int, int ) ) ); + + connect( m_seq, SIGNAL( signalOVEPitchBend( int, long, int, int ) ), + this, SLOT( ctlChangeEvent( int, long, int, int, int ) ) ); + + connect( m_seq, SIGNAL( signalOVEProgram( int, long, int, int ) ), + this, SLOT( programEvent( int, long, int, int ) ) ); + + connect( m_seq, SIGNAL( signalOVETimeSig( int, long, int, int ) ), + this, SLOT( timeSigEvent( int, long, int, int ) ) ); + + connect( m_seq, SIGNAL( signalOVETempo( long, int ) ), + this, SLOT( tempoEvent( long, int ) ) ); + + connect( m_seq, SIGNAL( signalOVETrackPatch( int, int, int ) ), + this, SLOT( trackPatch( int, int, int ) ) ); + + connect( m_seq, SIGNAL( signalOVETrackVol( int, int, int ) ), + this, SLOT( trackVol( int, int, int ) ) ); + + connect( m_seq, SIGNAL( signalOVETrackBank( int, int, int ) ), + this, SLOT( trackBank( int, int, int ) ) ); + + connect( m_seq, SIGNAL( signalOVENewTrack( QString, int, int, int, int, int, bool, bool, bool ) ), + this, SLOT( trackStart( QString, int, int, int, int, int, bool, bool, bool ) ) ); + +} + +oveReader::~oveReader() +{ + printf( "destroy oveReader\n" ); + delete m_seq; +} + +void oveReader::read( QString &fileName ) +{ + m_seq->readFromFile( fileName ); +} + +// Slots below. +void oveReader::timeSigEvent( int bar, long tick, int num, int den ) +{ + timeSigHandler( tick, num, den ); +} + +void oveReader::tempoEvent( long tick, int tempo ) +{ + tempoHandler( tick, tempo / 100 ); +} + +void oveReader::errorHandler( const QString &errorStr ) +{ + printf( "MidiImport::readSMF(): got error %s\n", + errorStr.toStdString().c_str() ); +} + +void oveReader::ctlChangeEvent( int track, long tick, int chan, int ctl, int value ) +{ + CCHandler( tick, track, ctl, value ); +} + +void oveReader::pitchBendEvent( int track, long tick, int chan, int value ) +{ + CCHandler( tick, track, pitchBendEventId, value ); +} + +void oveReader::noteOnEvent( int track, long tick, int chan, int pitch, int vol ) +{ + + if( vol ) + { + insertNoteEvent( tick, chan, pitch, vol, track ); + } + else + { + addNoteEvent( tick, chan, pitch, vol, track ); + } +} + +void oveReader::noteOffEvent( int track, long tick, int chan, int pitch, int vol ) +{ + addNoteEvent( tick, chan, pitch, vol, track ); +} + +void oveReader::programEvent( int track, long tick, int chan, int patch ) +{ + programHandler( tick, chan, patch, track ); +} + +void oveReader::trackPatch( int track, int chan, int patch ) +{ + programHandler( 0, chan, patch, track ); +} + +void oveReader::trackBank( int track, int chan, int bank ) +{ + CCHandler( 0, track, bankEventId, bank ); +} + +void oveReader::trackStart( const QString &name, int track, int channel, int pitch, int velocity, int port, bool selected, bool muted, bool loop ) +{ + trackStartHandler(); + textHandler( 3, name, track ); +} + +void oveReader::trackVol( int track, int chan, int vol ) +{ + CCHandler( 0, track, volumeEventId, vol ); +} + +void oveReader::fileHeader( int quarter, int trackCount ) +{ + timeBaseHandler( quarter ); + pd.setMaximum( trackCount + preTrackSteps ); +} diff --git a/plugins/SmfImport/oveReader.h b/plugins/SmfImport/oveReader.h new file mode 100644 index 00000000000..4719ad49d84 --- /dev/null +++ b/plugins/SmfImport/oveReader.h @@ -0,0 +1,59 @@ +/* + * oveReader.h - support for importing Overture files + * + * Copyright (c) 2016-2017 Tony Chyi + * + * This file is part of LMMS - http://lmms.io + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program (see COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + */ + +#ifndef OVE_READER_H +#define OVE_READER_H + +#include + +#include "commonReader.h" + +class oveReader : public commonReader +{ + Q_OBJECT +public: + oveReader( TrackContainer *tc ); + ~oveReader(); + void read( QString &fileName ); +public slots: + void errorHandler( const QString& errorStr ); + void fileHeader( int quarter, int trackCount ); + void noteOnEvent( int track, long tick, int chan, int pitch, int vol ); + void noteOffEvent( int track, long tick, int chan, int pitch, int vol ); + void ctlChangeEvent( int track, long tick, int chan, int ctl, int value ); + void pitchBendEvent( int track, long tick, int chan, int value ); + void programEvent( int track, long tick, int chan, int patch ); + void timeSigEvent( int bar, long tick, int num, int den ); + void tempoEvent( long tick, int tempo ); + void trackPatch( int track, int chan, int patch ); + void trackVol( int track, int chan, int vol ); + void trackBank( int track, int chan, int bank ); + void trackStart( const QString& name, int track, int channel, int pitch, int velocity, + int port, bool selected, bool muted, bool loop ); + +private: + drumstick::QOve *m_seq; +}; + +#endif diff --git a/plugins/SmfImport/wrkReader.cpp b/plugins/SmfImport/wrkReader.cpp new file mode 100644 index 00000000000..8a09c10b0ae --- /dev/null +++ b/plugins/SmfImport/wrkReader.cpp @@ -0,0 +1,147 @@ +/* + * wrkReader.cpp - support for importing Cakewalk files + * + * Copyright (c) 2016-2017 Tony Chyi + * + * This file is part of LMMS - http://lmms.io + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program (see COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + */ + +#include +#include +#include +#include +#include + +#include "GuiApplication.h" +#include "TrackContainer.h" +#include "Engine.h" +#include "Song.h" +#include "AutomationPattern.h" +#include "MidiTime.h" +#include "MainWindow.h" + +#include "SmfMidiCC.h" +#include "SmfMidiChannel.h" + +#include "wrkReader.h" + +wrkReader::wrkReader( TrackContainer* tc ) : + commonReader( tc, commonReader::tr( "Importing %1 file..." ).arg( "Cakewalk" ) ), + m_seq( new drumstick::QWrk ) +{ + // Connect to slots. + connect( m_seq, SIGNAL( signalWRKError( QString ) ), + this, SLOT( errorHandler( QString ) ) ); + connect( m_seq, SIGNAL( signalWRKTimeBase( int ) ), + this, SLOT( timeBase( int ) ) ); + connect( m_seq, SIGNAL( signalWRKNote( int, long, int, int, int, int ) ), + this, SLOT( noteEvent( int, long, int, int, int, int ) ) ); + connect( m_seq, SIGNAL( signalWRKCtlChange( int, long, int, int, int ) ), + this, SLOT( ctlChangeEvent( int, long, int, int, int ) ) ); + connect( m_seq, SIGNAL( signalWRKPitchBend( int, long, int, int ) ), + this, SLOT( pitchBendEvent( int, long, int, int ) ) ); + connect( m_seq, SIGNAL( signalWRKProgram( int, long, int, int ) ), + this, SLOT( programEvent( int, long, int, int ) ) ); + connect( m_seq, SIGNAL( signalWRKTimeSig( int, int, int ) ), + this, SLOT( timeSigEvent( int, int, int ) ) ); + connect( m_seq, SIGNAL( signalWRKTempo( long, int ) ), + this, SLOT( tempoEvent( long, int ) ) ); + connect( m_seq, SIGNAL( signalWRKTrackVol( int, int ) ), + this, SLOT( trackVol( int, int ) ) ); + connect( m_seq, SIGNAL( signalWRKTrackBank( int, int ) ), + this, SLOT( trackBank( int, int ) ) ); + connect( m_seq, SIGNAL( signalWRKTrackName( int, const QString ) ), + this, SLOT() ); +} + +wrkReader::~wrkReader() +{ + printf( "destroy wrkReader\n" ); + delete m_seq; +} + +void wrkReader::read( QString &fileName ) +{ + m_seq->readFromFile( fileName ); +} + +// Slots below. + +void wrkReader::timeSigEvent( int bar, int num, int den ) +{ + int tick = bar * num * ticksPerBeat; + timeSigHandler( tick, num, den ); +} + +void wrkReader::tempoEvent( long time, int tempo ) +{ + tempoHandler( time, tempo / 100 ); +} + +void wrkReader::errorHandler( const QString &errorStr ) +{ + printf( "MidiImport::readSMF(): got error %s\n", + errorStr.toStdString().c_str() ); +} + +void wrkReader::ctlChangeEvent( int track, long tick, int chan, int ctl, int value ) +{ + CCHandler( tick, track, ctl, value ); +} + +void wrkReader::pitchBendEvent( int track, long tick, int chan, int value ) +{ + CCHandler( tick, track, pitchBendEventId, value ); +} + +void wrkReader::noteEvent( int track, long time, int chan, int pitch, int vol, int dur ) +{ + addNoteEvent( time, chan, pitch, vol, dur, track ); +} + +void wrkReader::programEvent( int track, long tick, int chan, int patch ) +{ + programHandler( tick, chan, patch, track ); +} + +void wrkReader::trackPatch( int track, int patch ) +{ + //programHandler(0, chan, patch, track); +} + +void wrkReader::trackBank( int track, int bank ) +{ + //CCHandler(0, track, 0, bank); +} + +void wrkReader::trackName( int track, QString name ) +{ + trackStartHandler(); + textHandler( 3, name, track ); +} + +void wrkReader::trackVol( int track, int vol ) +{ + CCHandler( 0, track, volumeEventId, vol ); +} + +void wrkReader::timeBase( int quarter ) +{ + timeBaseHandler( quarter ); +} diff --git a/plugins/SmfImport/wrkReader.h b/plugins/SmfImport/wrkReader.h new file mode 100644 index 00000000000..0a0666aa1f5 --- /dev/null +++ b/plugins/SmfImport/wrkReader.h @@ -0,0 +1,57 @@ +/* + * wrkReader.h - support for importing Cakewalk files + * + * Copyright (c) 2016-2017 Tony Chyi + * + * This file is part of LMMS - http://lmms.io + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program (see COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + */ + +#ifndef WRK_READER_H +#define WRK_READER_H + +#include + +#include "commonReader.h" + +class wrkReader : public commonReader +{ + Q_OBJECT +public: + wrkReader( TrackContainer *tc ); + ~wrkReader(); + void read( QString &fileName ); +public slots: + void errorHandler( const QString& errorStr ); + void timeBase( int timebase ); + void noteEvent( int track, long time, int chan, int pitch, int vol, int dur ); + void ctlChangeEvent( int track, long time, int chan, int ctl, int value ); + void pitchBendEvent( int track, long time, int chan, int value ); + void programEvent( int track, long time, int chan, int patch ); + void timeSigEvent( int bar, int num, int den ); + void tempoEvent( long time, int tempo ); + void trackPatch( int track, int patch ); + void trackVol( int track, int vol ); + void trackBank( int track, int bank ); + void trackName( int track, QString name ); + +private: + drumstick::QWrk *m_seq; +}; + +#endif diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 3ec3cb7ee9b..a156f0a7914 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -64,6 +64,8 @@ set(LMMS_SRCS core/Track.cpp core/TrackContainer.cpp core/VstSyncController.cpp + core/SmfMidiCC.cpp + core/SmfMidiChannel.cpp core/audio/AudioAlsa.cpp core/audio/AudioDevice.cpp diff --git a/src/core/SmfMidiCC.cpp b/src/core/SmfMidiCC.cpp new file mode 100644 index 00000000000..ac380d700c2 --- /dev/null +++ b/src/core/SmfMidiCC.cpp @@ -0,0 +1,81 @@ +/* + * SmfMidiCC.cpp - support for importing MIDI files + * + * Copyright (c) 2005-2014 Tobias Doerffel + * + * This file is part of LMMS - http://lmms.io + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program (see COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + */ + +#include + +#include "TrackContainer.h" +#include "AutomationTrack.h" +#include "AutomationPattern.h" +#include "MidiTime.h" + +#include "SmfMidiCC.h" + +SmfMidiCC::SmfMidiCC() : + at( NULL ), + ap( NULL ), + lastPos( 0 ) +{ } + +SmfMidiCC & SmfMidiCC::create(TrackContainer* tc, QString tn ) +{ + if( !at ) + { + // Keep LMMS responsive, for now the import runs + // in the main thread. This should probably be + // removed if that ever changes. + qApp->processEvents(); + at = dynamic_cast( Track::create( Track::AutomationTrack, tc ) ); + } + if( tn != "") { + at->setName( tn ); + } + return *this; +} + + +void SmfMidiCC::clear() +{ + at = NULL; + ap = NULL; + lastPos = 0; +} + +SmfMidiCC & SmfMidiCC::putValue( MidiTime time, AutomatableModel * objModel, float value ) +{ + if( !ap || time > lastPos + DefaultTicksPerTact ) + { + MidiTime pPos = MidiTime( time.getTact(), 0 ); + ap = dynamic_cast( + at->createTCO(0) ); + ap->movePosition( pPos ); + ap->addObject( objModel ); + } + + lastPos = time; + time = time - ap->startPosition(); + ap->putValue( time, value, false ); + ap->changeLength( MidiTime( time.getTact() + 1, 0 ) ); + + return *this; +} diff --git a/src/core/SmfMidiChannel.cpp b/src/core/SmfMidiChannel.cpp new file mode 100644 index 00000000000..8ebeb715382 --- /dev/null +++ b/src/core/SmfMidiChannel.cpp @@ -0,0 +1,112 @@ +/* + * SmfMidiChannel.cpp - support for importing MIDI files + * + * Copyright (c) 2005-2014 Tobias Doerffel + * + * This file is part of LMMS - http://lmms.io + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program (see COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + */ + +#include + +#include "InstrumentTrack.h" +#include "Pattern.h" +#include "Instrument.h" +#include "MidiTime.h" +#include "ConfigManager.h" + +#include "SmfMidiChannel.h" + +SmfMidiChannel::SmfMidiChannel() : + it( NULL ), + p( NULL ), + it_inst( NULL ), + isSF2( false ), + hasNotes( false ), + lastEnd( 0 ) +{ } + +SmfMidiChannel * SmfMidiChannel::create( TrackContainer* tc, QString tn ) +{ + if( !it ) { + // Keep LMMS responsive + qApp->processEvents(); + it = dynamic_cast( Track::create( Track::InstrumentTrack, tc ) ); + +#ifdef LMMS_HAVE_FLUIDSYNTH + it_inst = it->loadInstrument( "sf2player" ); + + if( it_inst ) + { + isSF2 = true; + it_inst->loadFile( ConfigManager::inst()->defaultSoundfont() ); + it_inst->childModel( "bank" )->setValue( 0 ); + it_inst->childModel( "patch" )->setValue( 0 ); + } + else + { + it_inst = it->loadInstrument( "patman" ); + } +#else + it_inst = it->loadInstrument( "patman" ); +#endif + trackName = tn; + if( trackName != "") { + it->setName( tn ); + } + lastEnd = 0; + // General MIDI default + it->pitchRangeModel()->setInitValue( 2 ); + } + return this; +} + +void SmfMidiChannel::addNote( Note & n ) +{ + if( !p || n.pos() > lastEnd + DefaultTicksPerTact ) + { + MidiTime pPos = MidiTime( n.pos().getTact(), 0 ); + p = dynamic_cast( it->createTCO( 0 ) ); + p->movePosition( pPos ); + } + else if( p && n.pos() < p->startPosition() ) + { + // Find the suitable Pattern. + for( TrackContentObject *tco : it->getTCOs() ) + { + // If there is any tco have larger index and less startPosition? + // So, no break. + if( tco->startPosition() <= n.pos() ) + p = dynamic_cast( tco ); + } + } + hasNotes = true; + lastEnd = n.pos() + n.length(); + n.setPos( n.pos( p->startPosition() ) ); + p->addNote( n, false ); +} + +void SmfMidiChannel::setName( QString tn ) +{ + if ( !tn.length() ) + { + it->setName( QT_TRANSLATE_NOOP("TrackContainer", "Track") ); + return; + } + it->setName( tn ); +} diff --git a/src/core/Song.cpp b/src/core/Song.cpp index ebcdedb78af..8685f75bb28 100644 --- a/src/core/Song.cpp +++ b/src/core/Song.cpp @@ -1187,6 +1187,10 @@ void Song::importProject() ConfigManager::inst()->userProjectsDir(), tr("MIDI sequences") + " (*.mid *.midi *.rmi);;" + + tr("Overture projects") + + " (*.ove);;" + + tr("Cakewalk projects") + + " (*.wrk);;" + tr("Hydrogen projects") + " (*.h2song);;" + tr("All file types") + @@ -1348,6 +1352,20 @@ void Song::exportProjectMidi() return; } + QMessageBox::StandardButton readyToExport; + readyToExport = QMessageBox::question( gui->mainWindow(), + tr( "Hint of MIDI export" ), + tr( "To export midi from LMMS, " + "Please ensure the following things.\n\n" + "1. Instrument of tracks to export should be " + "set to Sf2 Player.\n" + "2. Bassline or Beats will not be exported.\n" + "3. 16 tracks or less will be better.\n\n" + "Continue?"), + QMessageBox::Yes | QMessageBox::No ); + if( readyToExport == QMessageBox::No ) + return; + FileDialog efd( gui->mainWindow() ); efd.setFileMode( FileDialog::AnyFile ); diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 502d9bb5a92..b2b187d0974 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -308,12 +308,13 @@ void MainWindow::finalize() SLOT( exportProjectTracks() ), Qt::CTRL + Qt::SHIFT + Qt::Key_E ); - // temporarily disabled broken MIDI export - /*project_menu->addAction( embed::getIconPixmap( "midi_file" ), +#ifdef LMMS_HAVE_FLUIDSYNTH + project_menu->addAction( embed::getIconPixmap( "midi_file" ), tr( "Export &MIDI..." ), Engine::getSong(), SLOT( exportProjectMidi() ), - Qt::CTRL + Qt::Key_M );*/ + Qt::CTRL + Qt::Key_M ); +#endif project_menu->addSeparator(); project_menu->addAction( embed::getIconPixmap( "exit" ), tr( "&Quit" ),