diff --git a/README.md b/README.md
index ea76809..a4600bb 100644
--- a/README.md
+++ b/README.md
@@ -6,6 +6,10 @@ A list of software and resources for professional audio/video/live events produc
This list is provided to help you build your own GNU/Linux based A/V production environment. Most of the listed software is packaged for [Debian](http://debian.org/), and should be directly installable using your package manager. Software that can be run on other GNU/Linux distributions may also be added to the list. This list focuses on sound, video, lighting and live applications.
+Useful automation scripts may be found in the **[scripts](scripts/) directory**.
+
+Tutorials and howto guides about Linux multimedia software can be found on the **[Tutorials](tutorials.md) page**.
+
Unpackaged
▒ Only in KXStudio repositories
Non-free/closed source
@@ -41,7 +45,6 @@ This list is provided to help you build your own GNU/Linux based A/V production
- [Unsorted](#unsorted)
- [DOCUMENTATION](#documentation)
- [General doc/software/forums](#general-docsoftwareforums)
- - [Howtos](#howtos)
- [GLOSSARY](#glossary)
- [TODO](#todo)
- [LICENSE](#license)
@@ -255,7 +258,6 @@ two oscillator software synthesizer ([Homepage](http://code.google.com/p/amsynth
* [gwc](http://packages.debian.org/sid/gwc) - Audio file denoiser ([Homepage](http://gwc.sf.net))
http://panic.et.tudelft.nl/~costar/gramofile/ 404
* [declick](http://home.snafu.de/wahlm/dl8hbs/declick.html) - a dynamic digital declicker for audio sample files. ``
- * [loopcrossfade](https://gist.github.com/nk23x/b14f9305b7e2a1bc0727) - makes an audiofile loop itself (seamless by using a crossfade trick)
### Meters & Analysis
@@ -297,6 +299,7 @@ http://panic.et.tudelft.nl/~costar/gramofile/ 404
* [qmidiarp](http://packages.debian.org/wheezy/qmidiarp) - arpégiateur MIDI pour ALSA ([Homepage](http://qmidiarp.sourceforge.net/))
* [qmidinet](http://packages.debian.org/wheezy/qmidinet) - MIDI Network Gateway via UDP/IP Multicast ([Homepage](http://qmidinet.sourceforge.net/))
* [vmpk](http://packages.debian.org/wheezy/vmpk) - Virtual MIDI Piano Keyboard ([Homepage](http://vmpk.sourceforge.net/))
+ * [m2hpc](http://dominodesigns.info/m2hpc/index.html) - MIDI to Hydrogen Pattern Converter ``
## System utilities
@@ -628,39 +631,7 @@ http://panic.et.tudelft.nl/~costar/gramofile/ 404
* [Linux Audio Announces](http://lists.linuxaudio.org/listinfo/linux-audio-announce/) - email list to publish announcements.
* [Gentoo Pro-Audio Overlay](http://proaudio.tuxfamily.org/wiki/index.php?title=Main_Page) - Pro-audio support for Gentoo users
-### Howtos
-Read [System Setup](system-setup.md) for system related topics.
-
- * [LV2 plugins for mixing: My favorite basic plugins (by zthmusic) | Libre Music Production](http://libremusicproduction.com/articles/lv2-plugins-mixing-my-favorite-basic-plugins-zthmusic)
- * [Loop-based Music Composition With Linux, Pt. 1](http://www.linuxjournal.com/node/1000304)
- * [Dave Phillips' Articles and Tutorials - LinuxJournal](http://www.linuxjournal.com/users/dave-phillips)
- * [▶ Rough Mix with Calf FX - YouTube](https://www.youtube.com/watch?v=JR6mRkFkoBQ)
- * [▶ Hydrogen Drum Machine with CALF plugins - YouTube](https://www.youtube.com/watch?v=FJaSbPZgLnw)
- * [Puredata - FLOSS Manuals](https://flossmanuals.net/PureData/)
- * [Puredata tutorials](http://puredata.info/docs/tutorials)
- * [new year – with fluxus and mixxx](http://www.ponnuki.net/2011/01/year-event-fluxus-mixxx/)
- * [Rosegarden - DebianEdu Tutorials](https://wiki.debian.org/DebianEdu/Documentation/Manuals/Rosegarden)
- * [Making Music in the Rosegarden](http://www.penguinproducer.com/Blog/2011/11/making-music-in-the-rosegarden/)
- * [abcmidi Tutorial](http://wiki.li(https://wiki.debian.org/DebianEdu/Documentation/Manuals/Rosegarden)nuxaudio.org/wiki/abcmiditutorial)
- * [AlsaModularSynth - Making a vocoder](http://wiki.linuxaudio.org/wiki/amsvocodertutorial)
- * [Musescore tutorials](https://musescore.org/en/tutorials)
- * [Screencasting with FFmpeg, jack_capture and Xephyr [Linux-Sound]](http://wiki.linuxaudio.org/wiki/screencasttutorial)
- * [seq24: toggle sequences with a MIDI controller [Linux-Sound]](http://wiki.linuxaudio.org/wiki/seq24togglemiditutorial)
- * trackers: [Mod Tracking Tutorial -- Introduction](http://files.byondhome.com/Audiophiles/iainperegrine.modtracker_tutorial/modtracking_tutorial_intro.html)
- * trackers: [Mod Tracking Tutorial -- Introduction](http://files.byondhome.com/Audiophiles/iainperegrine.modtracker_tutorial/modtracking_tutorial_part1.html)
- * trackers: [Trackers and Linux. || kuro5hin.org](https://www.kuro5hin.org/story/2002/6/8/2524/90038)
- * trackers: [A Tutorial on Cutting Up a Breakbeat Using a Tracker || kuro5hin.org](https://www.kuro5hin.org/story/2005/11/13/182235/45)
- * trackers: [.:: Milkytracker Tutorial ::.](http://www.seele07.de/milkytutorial/data/start_here.html)
- * [Cover - Prodigy - Breath - LMMS - ZynAddSubFX - Linux - YouTube](http://www.youtube.com/watch?v=gxTxhv8H9X0)
- * [Getting Started With SooperLooper on Vimeo](http://vimeo.com/7315051)
- * [Introduction_to_Ardour_3.0_MIDI : Dan MacDonald : Free Download & Streaming : Internet Archive](http://www.archive.org/details/Introduction_to_Ardour_3.0_MIDI) * [01_PADsynth_strings.ogv - YouTube](http://www.youtube.com/watch?v=IA-7tpTfE-E)
- * [Linux music tutorial: seq24, part 1 - YouTube](http://www.youtube.com/watch?v=J2WDHS1wYeM)
- * [Seq24 Tutorial, Part 1 - setting loops - YouTube](http://www.youtube.com/watch?v=51W9PQfyMsg)
- * [Amsynth - Linux Synthesizer - YouTube](http://www.youtube.com/watch?v=YHR9hQVrRIQ)
- * [Ardour - Music Editing in Linux - Part #1 - YouTube](http://www.youtube.com/watch?v=43ES7p4ejX0)
- * [You, Too Can Learn Renoise: Video Tutorial](http://createdigitalmusic.com/2009/10/you-too-can-learn-renoise-video-tutorial-from-dac-makes-you-a-tracker/)
- * [Linux soft synth tutorial: part 6.1 - YouTube](http://www.youtube.com/watch?v=p6SoNX4bA1Y)
## GLOSSARY
@@ -689,7 +660,7 @@ Read [System Setup](system-setup.md) for system related topics.
* http://www.kvraudio.com/news/discodsp-updates-vertigo-additive-synth-to-r3-5-including-linux-support-29997
* http://www.kvraudio.com/news/discodsp-updates-discovery-pro-va-and-wave-synth-to-6-4-5-29782
* Package scripts from http://www.pjb.com.au/midi/
- * Group and package scripts from http://wiki.linuxaudio.org/wiki/script_midi2hydrogen, http://wiki.linuxaudio.org/wiki/script_lscp2rgd, http://wiki.linuxaudio.org/wiki/scripts_wav2specimen, http://wiki.linuxaudio.org/wiki/scripts_and_tools
+ * Group and package scripts from, http://wiki.linuxaudio.org/wiki/scripts_and_tools (partially done in scripts/)
* add software from http://bandshed.net/avlinux6-debs/
## LICENSE
diff --git a/migration.md b/migration.md
index fc0bf2d..0c72c2f 100644
--- a/migration.md
+++ b/migration.md
@@ -29,7 +29,6 @@ http://www.ibiblio.org/pub/Linux/apps/sound/editors/!INDEX.html
## Migrated
-la-fait
http://www.apodio.org/
http://www.apo33.org/apodio-site/?p=48
http://artistx.org/blog/
@@ -44,7 +43,6 @@ http://bandshed.net/forum/index.php?topic=3502.0
http://wiki.linuxaudio.org/apps/categories/distributions
http://wiki.linuxaudio.org/apps/categories/linux_audio_bundles_distributions_and_music_collections
http://wiki.linuxaudio.org/wiki/script_midi2hydrogen
-http://wiki.linuxaudio.org/wiki/open_discussion
http://easy.open.and.free.fr/didjix/features.html
http://wiki.linuxaudio.org/apps/screenshots
http://wiki.linuxaudio.org/apps/all/lat
@@ -55,6 +53,7 @@ http://wiki.linuxaudio.org/apps/all/lau
http://opensourcemusician.com/index.php/Main_Page
http://linuxaudio.org/resources
http://wiki.linuxaudio.org/site/about
+http://wiki.linuxaudio.org/wiki/open_discussion
http://wiki.linuxaudio.org/wiki/request
http://wiki.linuxaudio.org/apps/logos
http://jackaudio.org/realtime_vs_realtime_kernel
@@ -164,6 +163,9 @@ http://jzu.free.fr/outils.html
http://gwc.sourceforge.net/
https://gist.github.com/coderofsalvation/7740333
http://wiki.linuxaudio.org/wiki/tutorials/start
+http://wiki.linuxaudio.org/wiki/script_lscp2rgd
+http://wiki.linuxaudio.org/wiki/scripts_wav2specimen
+
audiocutter: abandoned, sf 404
DAP Richard Kent's 404
glame abandoned 2007 http://sourceforge.net/projects/glame/files/
diff --git a/scripts/README.md b/scripts/README.md
new file mode 100644
index 0000000..8d64ece
--- /dev/null
+++ b/scripts/README.md
@@ -0,0 +1,3 @@
+ ### Scripts
+
+ * [midi-drums-to-h2song-hydrogen](http://italianmafia.altervista.org/blog/download.php?get=midi2hydrogen.tar.gz) - Usage: `python midi2hydrogen.py input.mid output.h2song`
\ No newline at end of file
diff --git a/scripts/linuxsampler-instrument-to-rosegarden.py b/scripts/linuxsampler-instrument-to-rosegarden.py
new file mode 100644
index 0000000..fc84942
--- /dev/null
+++ b/scripts/linuxsampler-instrument-to-rosegarden.py
@@ -0,0 +1,53 @@
+
+#!/usr/bin/python
+# convert a lscp (linux sampler) instrument file to rgd (rosegarden sequencer) instrument file
+# from http://wiki.linuxaudio.org/wiki/script_lscp2rgd
+# from https://bb.linuxsampler.org/viewtopic.php?f=7&t=66
+
+import sys, gzip, re
+
+pattern = re.compile(r"MAP[\s]*MIDI_INSTRUMENT[\s]*([\d]+)[\s]*([\d]+)[\s]*([\d]+)[\s]*([\w]+)[\s]*'([\S]+)'[\s]*([\d]+)[\s]*([\d]+.[\d]+)[\s]*([\w]+)[\s]*'(.+)'[\s]*$")
+
+preamble = '''
+
+
+
+
+
+
+'''
+postamble = '''
+
+
+
+'''
+rgd = open('%s.rgd' % (sys.argv[1]), 'w')
+out = gzip.GzipFile(mode='w', filename = "audio/x-rosegarden-device", fileobj=rgd)
+out.filename = "audio/x-rosegarden-device"
+
+out.write(preamble)
+total_programs = 0
+current_bank = -1
+lscp = open(sys.argv[1], 'r')
+for line in lscp:
+ line = line.strip()
+ tokens = line.split(' ')
+ if tokens[0] == '#' and tokens[1] and len(tokens) == 2:
+ bank_name = tokens[1]
+ elif tokens[0] == 'MAP' and tokens[1] == 'MIDI_INSTRUMENT':
+ total_programs += 1
+ m = pattern.match(line)
+ bank, program, name = int(m.group(2)), int(m.group(3)), m.group(9).replace(r"\'", "'")
+ if bank != current_bank:
+ if current_bank != -1:
+ out.write(' \n\n')
+ current_bank = int(bank)
+ out.write(' \n' % (bank_name, 0, bank))
+ out.write(' \n' % (program, name))
+out.write(' \n')
+out.write(postamble)
+lscp.close()
+out.close()
+rgd.close()
+
+print "%d programs in %d banks" % (total_programs, current_bank+1)
\ No newline at end of file
diff --git a/scripts/loopcrossfade.sh b/scripts/loopcrossfade.sh
new file mode 100644
index 0000000..9e712b8
--- /dev/null
+++ b/scripts/loopcrossfade.sh
@@ -0,0 +1,33 @@
+# makes a audiofile to loop itself (seamless by using a crossfade trick) (needs sox audio utilities)
+# @dependancy sox
+# source https://gist.github.com/nk23x/b14f9305b7e2a1bc0727
+# Usage: ./loopcrossfade [outputdir]
+#
+
+loopcrossfade(){
+ input="$1"; faderatio="$2"; outputdir="$3"; tmpinput="/tmp/$(basename "$input").loopcrossfade.wav"
+ [[ ! -f "$input" ]] && echo "cannot find $1" && exit 1
+ valid=$(echo "$faderatio > 1.99" | bc -l );
+ (( $valid == 0 )) && echo "faderatio should be 2.0 or bigger" && exit 1
+ [[ -d "$outputdir" ]] && outputfile="$outputdir/$(basename "$input")_loop.$faderatio.wav" \
+ || outputfile="$input""_loop.$faderatio.wav"
+ # prepare input
+ format="-c 2 -e signed -b 16 -r 44100"
+ sox "$input" ${format} "$tmpinput" &&
+ samples="$(soxi "$tmpinput" | grep Duration | cut -d' ' -f11 )"
+ fadetime="$( echo "$samples/$faderatio" | bc )"
+ fadetimehalf="$( echo "$fadetime/2" | bc )"
+ middle="$( echo "$samples-$fadetime" | bc )"
+
+ # get middle part + add fadein
+ sox "$tmpinput" ${format} "$tmpinput.lmid.wav" fade t "$fadetimehalf"s trim 0 "$middle"s
+ # get end (+fadeout)
+ sox "$tmpinput" ${format} "$tmpinput.lend.wav" trim "$middle"s "$fadetime"s
+ sox "$tmpinput.lend.wav" "$tmpinput.lendfadeout.wav" fade t 0 0 "$fadetime"s
+ # combine together
+ sox -m "$tmpinput.lendfadeout.wav" "$tmpinput.lmid.wav" "$outputfile" norm
+ echo "written $outputfile"
+ rm /tmp/*.loopcrossfade.*
+}
+
+loopcrossfade "$1" "$2" "$3"
\ No newline at end of file
diff --git a/scripts/midi-to-h2song-hydrogen.pl b/scripts/midi-to-h2song-hydrogen.pl
new file mode 100644
index 0000000..a1f7897
--- /dev/null
+++ b/scripts/midi-to-h2song-hydrogen.pl
@@ -0,0 +1,982 @@
+#Convert midi drums to *.h2song hydrogen drum sequencer format
+#########################################################################################
+# ____________________________________________________________________
+# / \
+# | ____ __ ___ _____ / ___ ___ |
+# | ____ / \/ \ ' / \ / / /__ / \ / \ |
+# | / _ \ / / / / / / ___/ \__ / /____/ / / |
+# | / |_ / / / / / / / / / \ / / /____/ |
+# | \____/ / / \/_/ / \__/ _____/ \__/ \___/ / |
+# | / |
+# | |
+# | Copyright (c) 2007 Herve Masson, MindStep SARL |
+# | rvmindstep@users.sourceforge.net |
+# \____________________________________________________________________/
+#
+# 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; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+#########################################################################################
+#
+# midi2hydrogen.pl - MIDI file conversion script for hydrogen (hydrogen-music.org)
+#
+# ==> Read the usage message below for more info
+#
+# (Note: was created on ubuntu 7.x using perl 5.8, with the additional CPAN
+# packages XML-Simple and MIDI-Perl)
+#
+# -=-
+#
+# Ideas/TODOs:
+#
+# - some quantization would probably be useful
+# - detect identical patterns (that's tricky)
+# - load instruments from multiple kits
+# - ~home/.midi2hydrogen.cfg config file instead of options
+# - find a way to implement some midi controls (volume, pan,...)
+#
+#########################################################################################
+
+use strict;
+use Data::Dumper;
+use Getopt::Long;
+use MIDI;
+
+my($ME)="midi2hydrogen.pl";
+my($USAGE)="
+Usage: perl ${ME} [options] file.mid file.h2song
+ perl ${ME} [options] file.mid
+
+This script extracts the drum track(s) from a midi file and generates an hydrogen
+.h2song file. When the h2song file name is omitted, the program simply reports
+information that tell how this file would be converted, without actually generating
+anything. This information contains, amongst other things, the instrument
+mapping between MIDI and H2, which can be altered using the following options:
+
+-h, --help
+ Prints this message.
+
+-v, --verbose
+ Increases verbosity.
+
+
+";
+
+#########################################################################################
+#
+# GLOBAL CONFIGURATION - following variables control important behaviors
+#
+#########################################################################################
+
+package Cfg;
+
+use vars qw($KITNAME $KITPATH %INSTRUMENTMAP %DFLSONG %DFLINSTRUMENT %DFLLAYER %DFLNOTE);
+
+# This is the location where the h2 drumkits are
+# ==============================================
+
+$KITPATH="/usr/share/hydrogen/data/drumkits";
+
+# Which H2 kit we are using
+# =========================
+
+$KITNAME="GMkit";
+
+# This is the instrument mapping between MIDI and (GMkit)h2:
+# ==========================================================
+
+%INSTRUMENTMAP=
+(
+ # MIDI => H2 # General midi name
+ #---------------------------------------
+ 35 => 0, # Acoustic Bass Drum
+ 36 => 0, # Bass Drum 1
+ 37 => 1, # Side Stick
+ 38 => 2, # Acoustic Snare
+ 39 => 3, # Hand Clap
+ 40 => 4, # Electric Snare
+ 41 => 5, # Low Floor Tom
+ 42 => 6, # Closed Hi-Hat
+ 43 => 9, # High Floor Tom
+ 44 => 8, # Pedal Hi-Hat
+ 45 => 5, # Low Tom
+ 46 => 10, # Open Hi-Hat
+ 47 => 7, # Low-Mid Tom
+ 48 => 7, # Hi-Mid Tom
+ 49 => 13, # Crash Cymbal 1
+ 50 => 9, # High Tom
+ 51 => 12, # Ride Cymbal 1
+ 52 => 15, # Chinese Cymbal
+ 53 => 14, # Ride Bell
+ 54 => undef, # Tambourin
+ 55 => 15, # Splash Cymbal
+ 56 => 11, # Cowbell
+ 57 => 15, # Crash Cymbal 2
+ 58 => undef, # Vibraslap
+ 59 => 12, # Ride Cymbal 2
+ 60 => undef, # Hi Bongo
+ 61 => undef, # Low Bongo
+ 62 => undef, # Mute Hi Conga
+ 63 => undef, # Open Hi Conga
+ 64 => undef, # Low Conga
+ 65 => undef, # High Timbale
+ 66 => undef, # Low Timbale
+ 67 => undef, # High Agogo
+ 68 => undef, # Low Agogo
+ 69 => undef, # Cabasa
+ 70 => undef, # Maracas
+ 71 => undef, # Short Whistle
+ 72 => undef, # Long Whistle
+ 73 => undef, # Short Guiro
+ 74 => undef, # Long Guiro
+ 75 => undef, # Claves
+ 76 => undef, # Hi Wood Block
+ 77 => undef, # Low Wood Block
+ 78 => undef, # Mute Cuica
+ 79 => undef, # Open Cuica
+ 80 => undef, # Mute Triangle
+ 81 => undef, # Open Triangle
+);
+
+# The following tables give default values for hydrogen structures
+# (which can't be obtained from the MIDI file)
+# ================================================================
+
+%DFLSONG=
+(
+ version => "0.9.3",
+ bpm => 120,
+ volume => 0.5,
+ metronomeVolume => 0.5,
+ name => "noname",
+ author => "unknown",
+ notes => "imported from midi",
+ loopEnabled => "true",
+ mode => "pattern",
+ humanize_time => 0,
+ humanize_velocity => 0,
+ swing_factor => 0,
+ delayFXEnabled => "false",
+ delayFXWetLevel => 1,
+ delayFXFeedback => 0.4,
+ delayFXTime => 48,
+);
+
+%DFLINSTRUMENT=
+(
+ volume => 1,
+ isMuted => 'false',
+ isLocked => 'false',
+ pan_L => 1,
+ pan_R => 1,
+ gain => 1,
+ FX1Level => 0,
+ FX2Level => 0,
+ FX3Level => 0,
+ FX4Level => 0,
+ Attack => 0,
+ Decay => 0,
+ Sustain => 1,
+ Release => 1000,
+ randomPitchFactor => 0,
+);
+
+%DFLLAYER=
+(
+ min => 0,
+ max => 1,
+ gain => 1,
+ pitch => 0,
+);
+
+%DFLNOTE=
+(
+ velocity => 0.8,
+ pan_L => 1,
+ pan_R => 1,
+ pitch => 0,
+ length => -1,
+);
+
+
+use vars qw($MIDI_DRUMCHAN $MIDI_CTRL_VOLUME $MIDI_CTRL_PAN %MIDI_CHANEVENTS);
+
+# Reserved channel number for drum
+# ================================
+
+$MIDI_DRUMCHAN=9;
+
+# MIDI controls
+# =============
+
+$MIDI_CTRL_VOLUME=7; # Channel Volume (formerly Main Volume)
+$MIDI_CTRL_PAN=10; # Pan
+
+
+# The following MIDI events are related to a MIDI channel:
+# ========================================================
+
+%MIDI_CHANEVENTS=
+(
+ note_off => 1,
+ note_on => 1,
+ key_after_touch => 1,
+ control_change => 1,
+ patch_change => 1,
+ channel_after_touch => 1,
+ pitch_wheel_change => 1,
+);
+
+
+#########################################################################################
+#
+# Some global variables
+#
+#########################################################################################
+
+package main;
+
+my($SONG,%OPTS);
+
+#########################################################################################
+#
+# Various utilities
+#
+#########################################################################################
+
+sub StripSpaces
+{
+ my($str)=shift;
+
+ $str =~ s/^\s+//;
+ $str =~ s/\s+$//;
+ return $str;
+}
+
+sub Warning
+{
+ my($fmt)=shift;
+ printf STDERR "Warning: $fmt\n",@_;
+}
+
+sub Error
+{
+ my($fmt)=shift;
+ printf STDERR "Error: $fmt\n",@_;
+}
+
+sub Trace
+{
+ my($fmt)=shift;
+ if($OPTS{verbose})
+ {
+ printf "[trace] $fmt\n",@_;
+ }
+}
+
+
+#########################################################################################
+#
+# Some extension to the MIDI::Track package
+#
+#########################################################################################
+
+package MIDI::Track;
+
+sub searchEvent
+{
+ my($self)=shift;
+ my($type)=shift;
+
+ my(@events)=$self->events();
+ my(@list);
+
+ foreach my $ev (@events)
+ {
+ if($ev->[0] eq $type)
+ {
+ push(@list,$ev);
+ last unless(wantarray);
+ }
+ }
+ if(wantarray)
+ {
+ return @list;
+ }
+ return $list[0];
+}
+
+sub label
+{
+ my($self)=shift;
+
+ $self->{label}=shift if(@_>0);
+ return $self->{label};
+}
+
+sub index
+{
+ my($self)=shift;
+
+ $self->{index}=shift if(@_>0);
+ return $self->{index};
+}
+
+#########################################################################################
+#
+# This class represents an hydrogen song
+#
+#########################################################################################
+
+package HydrogenSong;
+
+use XML::Simple;
+use Data::Dumper;
+
+
+sub _field_
+{
+ my($name)=shift;
+ my($self)=shift;
+
+ $self->{$name}=shift if(@_>0);
+ return $self->{$name};
+}
+
+sub bpm { return _field_("bpm",@_) }
+sub volume { return _field_("volume",@_) }
+sub version { return _field_("version",@_) }
+sub instruments { return _field_("instruments",@_) }
+sub patterns { return _field_("patterns",@_) }
+sub patternMap { return _field_("patternMap",@_) }
+sub notes { return _field_("notes",@_) }
+sub stats { return _field_("stats",@_) }
+
+sub new
+{
+ my($class)=shift;
+
+ my $self=bless({ %Cfg::DFLSONG },$class);
+ $self->loadKit($Cfg::KITNAME);
+ $self->notes([]);
+ $self->patterns([]);
+ $self->stats({});
+ return $self;
+}
+
+sub loadKit
+{
+ my($self)=shift;
+ my($dkname)=shift;
+
+ # Load the hygrogen kit
+ # ---------------------
+
+ my($ref)=XMLin("$Cfg::KITPATH/$dkname/drumkit.xml");
+ die "can't load kit $dkname" unless($ref);
+ my($map)=$ref->{instrumentList}->{instrument};
+
+ my(@list);
+ while(my($key,$value)=each(%{$map}))
+ {
+ next if($value->{filename} eq "");
+
+ my($ins)={ %Cfg::DFLINSTRUMENT, %$value };
+ delete($ins->{exclude});
+ $ins->{name}=$key;
+ $ins->{drumkit}=$dkname;
+ $ins->{layer}=
+ {
+ %Cfg::DFLLAYER,
+ filename => $ins->{filename},
+ };
+ delete($ins->{filename});
+ push(@list,$ins);
+ }
+
+ @list=sort { $a->{id} <=> $b->{id} } @list;
+ $self->instruments([@list]);
+}
+
+my(%WARNEDINS);
+
+sub addNote
+{
+ my($self)=shift;
+
+ my($time)=shift;
+ my($insnum)=shift;
+ my($velocity)=shift;
+
+ my($h2ins);
+ my($stats)=$self->stats();
+ $stats->{midi}->[$insnum]++;
+
+ unless(defined($h2ins=$Cfg::INSTRUMENTMAP{$insnum}))
+ {
+ unless($WARNEDINS{$insnum})
+ {
+ $WARNEDINS{$insnum}=1;
+ main::Warning("MIDI instrument $insnum has no hydrogen equivalence - dropped");
+ }
+ return;
+ }
+
+ $stats->{h2}->[$h2ins]++;
+ my($note)=
+ {
+ %Cfg::DFLNOTE,
+ velocity => $velocity,
+ instrument => $h2ins,
+ position => int($time*48),
+ };
+
+ push(@{$self->{notes}},$note);
+}
+
+sub finalize
+{
+ my($self)=shift;
+
+ my($notes)=$self->notes();
+ my($patsz)=32; # Max size of a pattern, in quarter-notes
+ my($maxpos)=$patsz*24; # 24 positions per quarter-note
+
+ if(@$notes==0)
+ {
+ die "the song does not contain any note";
+ }
+
+ # Slice the song in fixed size patterns
+ my($offset)=0;
+ my(@patterns,%seqs);
+ my(@list)=@{$notes};
+
+ while(@list>=0)
+ {
+ my($note)=$list[0];
+
+ if(defined($note))
+ {
+ # Position relative to pattern begining
+ my($relpos)=$note->{position}-$offset;
+ if($relpos<$maxpos)
+ {
+ # This fit in the current pattern
+ my($ins)=$note->{instrument};
+ unless($seqs{$ins})
+ {
+ $seqs{$ins}=[];
+ }
+ $note->{position}=$relpos;
+ push(@{$seqs{$ins}},$note);
+ shift(@list);
+ next;
+ }
+ }
+
+ my(@seqs)=map { $seqs{$_} } (0...31);
+
+ push(@patterns,
+ {
+ name => sprintf("Pattern %d",scalar(@patterns)),
+ index => scalar(@patterns),
+ sequences => [@seqs],
+ size => $patsz*24,
+ });
+
+ $offset+=$maxpos;
+ %seqs=();
+ last if(@list==0);
+ }
+
+ $self->patterns([@patterns]);
+ $self->patternMap([@patterns]);
+}
+
+
+sub asString
+{
+ my($self)=shift;
+
+ my(@list);
+ push(@list,"");
+
+ while(my($key,$value)=each(%{$self}))
+ {
+ unless(ref($value))
+ {
+ push(@list,"<$key>$value$key>");
+ }
+ }
+
+ push(@list,"");
+ foreach my $ins (@{$self->instruments()})
+ {
+ push(@list,"");
+ while(my($key,$value)=each(%{$ins}))
+ {
+ unless(ref($value))
+ {
+ push(@list,"<$key>$value$key>");
+ }
+ }
+ push(@list,"");
+
+ push(@list,"");
+ while(my($key,$value)=each(%{$ins->{layer}}))
+ {
+ unless(ref($value))
+ {
+ push(@list,"<$key>$value$key>");
+ }
+ }
+ push(@list,"");
+
+ push(@list,"");
+ }
+ push(@list,"");
+
+ push(@list,"");
+ foreach my $pat (@{$self->patterns()})
+ {
+ push(@list,"");
+ push(@list,"$pat->{name}");
+ push(@list,"$pat->{size}");
+ push(@list,"");
+
+ foreach my $seq (@{$pat->{sequences}})
+ {
+ push(@list,"");
+ push(@list,"");
+
+ foreach my $note (@{$seq})
+ {
+ push(@list,"");
+ while(my($key,$value)=each(%{$note}))
+ {
+ push(@list,"<$key>$value$key>");
+ }
+ push(@list,"");
+ }
+
+ push(@list,"");
+ push(@list,"");
+ }
+
+ push(@list,"");
+ push(@list,"");
+ }
+ push(@list,"");
+
+ push(@list,"");
+ foreach my $pat (@{$self->patternMap()})
+ {
+ push(@list,"");
+ push(@list,"$pat->{name}");
+ push(@list,"");
+ }
+ push(@list,"");
+
+ push(@list,"");
+
+ my($tabs)=0;
+ my(@out);
+ foreach my $item (@list)
+ {
+ if($item =~ m|^|)
+ {
+ # Closure
+ $tabs--;
+ }
+ push(@out,sprintf("%s%s",(" "x$tabs),$item));
+ if($item =~ m|^<[^/]|)
+ {
+ if(($item !~ m||) && ($item !~ m|/>|))
+ {
+ $tabs++;
+ }
+ }
+ }
+ push(@out,"");
+ return join("\n",@out);
+}
+
+sub saveAs
+{
+ my($self)=shift;
+ my($fname)=shift;
+
+ my($fd);
+ unless(open($fd,">$fname"))
+ {
+ die "could not save file $fname - $!";
+ }
+ print $fd $self->asString();
+ close($fd);
+ return $self;
+}
+
+sub showInstrumentMapping
+{
+ my($self)=shift;
+
+ my($listr)=$self->instruments();
+ my(@list)=@{$listr};
+
+ printf("Instruments mapping:\n");
+ printf("====================\n\n");
+
+ # Computes some statistics
+ # ------------------------
+
+ my($stats)=$self->stats();
+
+ # Display the instrument mapping
+ # ------------------------------
+
+ my(%htable);
+ foreach my $ins (@list)
+ {
+ $htable{$ins->{id}}=$ins;
+ }
+
+ my($line)=" +--------------------------------+--------------------------------+-------+\n";
+ print($line);
+ printf(" | General midi instruments | Hydrogen instrument | Notes |\n");
+ print($line);
+ my(%used);
+ foreach my $key (sort { $a <=> $b } keys(%MIDI::notenum2percussion))
+ {
+ my($value)=$MIDI::notenum2percussion{$key};
+ my($hid)=$Cfg::INSTRUMENTMAP{$key};
+ my($mapping)="-";
+
+ if(defined($hid))
+ {
+ my($ins)=$htable{$hid};
+ if($ins)
+ {
+ $used{$ins}=1;
+ $mapping=sprintf("(%02d) %s",$hid,$ins->{name});
+ }
+ }
+
+ my($count)=$stats->{midi}->[$key];
+ $count="-" unless($count>0);
+ printf(" | %-30s | %-30s | %5s |\n",
+ sprintf("(%0d) %s",$key,$value),
+ $mapping,$count);
+ }
+ foreach my $ins (@list)
+ {
+ next if($used{$ins});
+ my($mapping)=sprintf("(%02d) %s",$ins->{id},$ins->{name});
+ printf(" | %-30s | %-30s | %5s |\n","-",$mapping,"-");
+ }
+ print($line);
+ printf("\n\n");
+}
+
+#--------------------------------------------------------------------------------
+#
+# Show MIDI file information
+# ==========================
+#
+# - midi file content (tracks, channels and patches)
+#
+#--------------------------------------------------------------------------------
+
+package main;
+
+sub ShowSongInfo
+{
+ my($song)=shift;
+
+ my(@tracks)=$song->tracks();
+ my($i)=0;
+ my($ev);
+
+ my($ticks)=$song->ticks();
+
+ printf("General information:\n");
+ printf("====================\n\n");
+ printf(" Midi clock : %d ticks/quater-note\n",$ticks);
+ printf(" Tempo : %d\n",$SONG->bpm());
+ printf(" H2 kit : %s/%s\n","$Cfg::KITPATH/$Cfg::KITNAME");
+ printf("\n\n");
+
+ printf("MIDI Tracks:\n");
+ printf("============\n\n");
+
+ my($line)=" +----+-----+--------------------------------+------------------------------------+";
+ printf("%s\n",$line);
+ printf(" | T# | Ch# | Track Name | General Midi Patch |\n");
+ printf("%s\n",$line);
+
+ foreach my $t (@tracks)
+ {
+ my($name)="(noname)";
+ my($patch)="";
+ my($chan)="";
+ my($gmname)=""; # General midi name
+
+ if(defined($ev=$t->searchEvent("track_name")))
+ {
+ $name=StripSpaces($ev->[2]);
+ }
+ if(defined($ev=$t->searchEvent("patch_change")))
+ {
+ $chan=$ev->[2];
+ if($chan == $Cfg::MIDI_DRUMCHAN)
+ {
+ $patch="-";
+ $gmname="(drum channel)";
+ }
+ else
+ {
+ $patch=$ev->[3];
+ $gmname=$MIDI::number2patch{$patch};
+ }
+ }
+
+ printf(" | %2d | %3s | %-30s | %-3s %-30s |\n",$i,$chan,$name,$patch,$gmname);
+ $t->label($name);
+ $t->index($i);
+ $i++;
+ }
+ printf("%s\n",$line);
+ printf(" (T#=Track number, Ch#=MIDI channel number)\n");
+ printf("\n\n");
+
+ $SONG->showInstrumentMapping();
+}
+
+my(%WARNCTRL);
+
+sub ProcessEvent_control_change
+{
+ my($ctx)=shift;
+ my($ev)=shift;
+
+ my($param)=$ev->{param}->[0]; # MIDI control identifier
+ my($value)=$ev->{param}->[1]; # control value
+
+ if($param == $Cfg::MIDI_CTRL_VOLUME)
+ {
+ Trace("set volume $value - NOT IMPLEMENTED");
+ }
+ elsif($param == $Cfg::MIDI_CTRL_PAN)
+ {
+ Trace("set pan $value - NOT IMPLEMENTED");
+ }
+ else
+ {
+ unless($WARNCTRL{$param})
+ {
+ $WARNCTRL{$param}=1;
+ Trace("ignoring unknown MIDI control %d",$param);
+ }
+ }
+}
+
+sub ProcessEvent_note_on
+{
+ my($ctx)=shift;
+ my($ev)=shift;
+
+ my($note)=$ev->{param}->[0]; # For drum, this corresponds to instrument selection
+ my($velocity)=$ev->{param}->[1]; # Usually corresponds to note volume
+
+ if($ev->{channel} != $Cfg::MIDI_DRUMCHAN)
+ {
+ # Ignore non-drum events
+ return;
+ }
+
+ $velocity=int($velocity/12.7)/10; # Convert velocity between 0 and 127
+
+ my($ticks)=$ctx->{song}->ticks();
+ my($tm)=$ev->{tickstamp}/$ticks;
+
+ $SONG->addNote($tm,$note,$velocity);
+ return 1;
+}
+
+sub ProcessEvent_set_tempo
+{
+ my($ctx)=shift;
+ my($ev)=shift;
+
+ if($ctx->{convms})
+ {
+ Warning("tempo is defined more than once - secundary definitions ignored");
+ return;
+ }
+
+ if($ev->{tickstamp} != 0)
+ {
+ Warning("tempo is set after song begining - ignored");
+ return;
+ }
+
+ my($tempo)=$ev->{param}->[0]; # Number of micro-seconds per quarter-note
+
+ # Get the number of ticks per quarter-note
+ my($ticks)=$ctx->{song}->ticks();
+
+ # Compute the conversion ratio to obtain milliseconds from tick counts
+ $ctx->{convms}=$tempo/(1000*$ticks);
+
+ my($qnms)=$ticks*$ctx->{convms};
+ my($bpm)=int(60000/$qnms);
+
+ $ctx->{tempo}=$bpm;
+ Trace("set tempo ${bpm} bpm (one quarter-note lasts %dms)",$qnms,$bpm);
+
+ $SONG->bpm($bpm);
+}
+
+sub ProcessSong
+{
+ my($song)=shift;
+ my(@tracks)=$song->tracks();
+
+ # ------------------------------------------------------------------------------------
+ # > Combine all tracks into a single grand event list
+ # > (we need to process the tempo changes and such, which might be on other tracks)
+ # ------------------------------------------------------------------------------------
+
+ my($songticks)=$song->ticks(); # Ticks per quarter-note
+ my(@events);
+
+ foreach my $track (@tracks)
+ {
+ my($index)=0;
+ my(@list)=$track->events();
+ my($tnum)=$track->index();
+ my($abstime)=0;
+
+ foreach my $ev (@list)
+ {
+ my(@ev)=@{$ev};
+ my($type)=shift(@ev);
+ my($reltime)=shift(@ev); # Relative timestamp
+ $abstime += $reltime;
+ my($notestamp)=$abstime/$songticks;
+
+ my($chan);
+ if($Cfg::MIDI_CHANEVENTS{$type})
+ {
+ $chan=shift(@ev);
+ }
+
+ push(@events,
+ {
+ type => $type, # Type of the MIDI event (ex: 'patch_change')
+ tickstamp => $abstime, # Absolute timestamp of the event, in number of ticks
+ notestamp => $notestamp, # Absolute timestamp, in quarter-notes
+ tracknum => $tnum,
+ index => $index++,
+ channel => $chan, # Channel number, when appropriate
+ param => [@ev],
+ });
+ }
+ }
+
+ # Sort all events in time order
+ @events=sort { $a->{tickstamp} <=> $b->{tickstamp} } @events;
+
+ # ------------------------------------------------------------------------------------
+ # > Process individual events
+ # ------------------------------------------------------------------------------------
+
+ my($ctx)={ song => $song };
+ foreach my $ev (@events)
+ {
+ my($type)=$ev->{type};
+ my($proc);
+
+ unless($proc=__PACKAGE__->can("ProcessEvent_$type"))
+ {
+ # We don't want this event type
+ next;
+ }
+ &$proc($ctx,$ev);
+ }
+ print "\n";
+}
+
+#--------------------------------------------------------------------------------
+#
+# Program entry point - command line parsing
+#
+#--------------------------------------------------------------------------------
+
+my(@OPTIONS)=
+(
+ "help|h",
+ "verbose|v",
+);
+
+Getopt::Long::Configure("bundling");
+
+unless(GetOptions(\%OPTS,@OPTIONS))
+{
+ print STDERR $USAGE;
+ exit(1);
+}
+
+if($OPTS{help})
+{
+ print $USAGE;
+ exit(0);
+}
+
+my($midiFile,$h2File)=@ARGV;
+
+if((@ARGV!=1) && (@ARGV!=2))
+{
+ Error("Invalid number of parameters");
+ print STDERR $USAGE;
+ exit(1);
+}
+
+# 1) Read the MIDI file
+# =====================
+
+$SONG=HydrogenSong->new();
+my($song)=eval { MIDI::Opus->new({ from_file => $midiFile }) };
+unless($song)
+{
+ Error("error while loading midi file $midiFile - $@");
+ exit(1);
+}
+
+ProcessSong($song);
+
+if($h2File)
+{
+ unless($h2File =~ /\.h2song$/)
+ {
+ $h2File="$h2File.h2song";
+ }
+ $SONG->finalize();
+ print "Saving h2song file: $h2File...\n";
+ $SONG->saveAs($h2File);
+}
+else
+{
+ ShowSongInfo($song);
+}
\ No newline at end of file
diff --git a/scripts/wavs-to-specimen.py b/scripts/wavs-to-specimen.py
new file mode 100644
index 0000000..42312ae
--- /dev/null
+++ b/scripts/wavs-to-specimen.py
@@ -0,0 +1,193 @@
+#!/usr/bin/env python
+# -*- coding: latin-1 -*-
+# dir2beef - by Atte André Jensen
+# source http://wiki.linuxaudio.org/wiki/scripts_wav2specimen
+# found at lau-ml, 25. April 2008 15:35
+# # how to use:
+# put it in yout path and type the following in a terminal:
+# python dir2beef.py --help
+
+import os, optparse, sys, os.path, wave
+
+
+parser = optparse.OptionParser()
+
+if '-?' in sys.argv:
+ sys.argv.remove('-?')
+ sys.argv.append('-h')
+
+parser.add_option('-d','--directory-containing-wavs', dest="wavDir", default='.')
+parser.add_option('-l','--lowest-note-of-mapping', dest="startNote", default='36')
+parser.add_option('-f','--force-overwrite', action="store_true", dest="forceOverwrite", default=False)
+parser.add_option('-s','--stdout', action="store_true", dest="stdOut", default=False)
+parser.add_option('-o','--outfile', dest="outFile")
+(options, args) = parser.parse_args()
+
+def getDir(dir):
+ result = []
+ import os.path
+ path = os.path.abspath(dir) + '/'
+ list = os.listdir(dir)
+ for file in list:
+ (root, ext) = os.path.splitext(file)
+ if os.path.isfile(path + file) and ext in ['.wav','.WAV']:
+ result.append(path + file)
+ return result
+
+
+
+
+
+header = """
+"""
+patch = """
+ %s
+ %s
+ 0
+ %s
+ 1,000000
+ 0,000000
+ 5
+ 0
+ 0
+ 0
+ %s
+ %s
+ 0
+ %s
+ 0
+ %s
+ 1,000000
+ 0,000000
+ 0,000000
+ 2
+ no
+ 0,050000
+ no
+ no
+ 0,000000
+ no
+ 0,000000
+ 0,000000
+ 1,000000
+ 0,000000
+ 0,000000
+ 0,000000
+ 0,000000
+ no
+ 1,000000
+ 0,000000
+ 0,000000
+ 1,000000
+ 1,000000
+ no
+ no
+ no
+ sine
+ 0,000000
+ no
+ 0,000000
+ 0,000000
+ 1,000000
+ 0,000000
+ 0,000000
+ 0,000000
+ 0,000000
+ no
+ 0,000000
+ 0,000000
+ 0,000000
+ 1,000000
+ 1,000000
+ no
+ no
+ no
+ sine
+ 0,000000
+ no
+ 0,000000
+ 0,000000
+ 1,000000
+ 0,000000
+ 0,000000
+ 0,000000
+ 0,000000
+ no
+ 0,000000
+ 0,000000
+ 0,000000
+ 1,000000
+ 1,000000
+ no
+ no
+ no
+ sine
+ 0,000000
+ no
+ 0,000000
+ 0,000000
+ 1,000000
+ 0,000000
+ 0,000000
+ 0,000000
+ 0,000000
+ no
+ 0,000000
+ 0,000000
+ 0,000000
+ 1,000000
+ 1,000000
+ no
+ no
+ no
+ sine
+ 0,000000
+ no
+ 0,000000
+ 0,000000
+ 1,000000
+ 0,000000
+ 0,000000
+ 0,000000
+ 0,000000
+ no
+ 0,000000
+ 0,000000
+ 0,000000
+ 1,000000
+ 1,000000
+ no
+ no
+ no
+ sine
+ """
+footer = ""
+
+# --- the action ----
+output = []
+output.append(header)
+note = int(options.startNote)
+for file in getDir(options.wavDir):
+ (head, tail) = os.path.split(file)
+ (basename, ext) = os.path.splitext(tail)
+ wav = wave.open(file)
+ end = wav.getnframes() -1
+ values = (basename,file,note, note, note, end, end)
+ output.append(patch % values)
+ note = note + 1
+
+output.append(footer)
+
+if options.stdOut == True:
+ print '\n'.join(output)
+ sys.exit()
+
+if not options.outFile:
+ options.outFile = options.wavDir.strip('/').split('/')[-1:][0] + '.beef'
+
+if not os.path.exists(options.outFile) or options.forceOverwrite:
+ FILE = open(options.outFile,"w")
+ for line in output:
+ FILE.write(line)
+else:
+ print 'outfile "' + options.outFile + '" exists, use -f to force overwrite'
\ No newline at end of file
diff --git a/tutorials.md b/tutorials.md
new file mode 100644
index 0000000..aca86b3
--- /dev/null
+++ b/tutorials.md
@@ -0,0 +1,33 @@
+### Tutorials
+
+Read [System Setup](system-setup.md) for system related topics.
+
+ * [LV2 plugins for mixing: My favorite basic plugins (by zthmusic) | Libre Music Production](http://libremusicproduction.com/articles/lv2-plugins-mixing-my-favorite-basic-plugins-zthmusic)
+ * [Loop-based Music Composition With Linux, Pt. 1](http://www.linuxjournal.com/node/1000304)
+ * [Dave Phillips' Articles and Tutorials - LinuxJournal](http://www.linuxjournal.com/users/dave-phillips)
+ * [▶ Rough Mix with Calf FX - YouTube](https://www.youtube.com/watch?v=JR6mRkFkoBQ)
+ * [▶ Hydrogen Drum Machine with CALF plugins - YouTube](https://www.youtube.com/watch?v=FJaSbPZgLnw)
+ * [Puredata - FLOSS Manuals](https://flossmanuals.net/PureData/)
+ * [Puredata tutorials](http://puredata.info/docs/tutorials)
+ * [new year – with fluxus and mixxx](http://www.ponnuki.net/2011/01/year-event-fluxus-mixxx/)
+ * [Rosegarden - DebianEdu Tutorials](https://wiki.debian.org/DebianEdu/Documentation/Manuals/Rosegarden)
+ * [Making Music in the Rosegarden](http://www.penguinproducer.com/Blog/2011/11/making-music-in-the-rosegarden/)
+ * [abcmidi Tutorial](http://wiki.li(https://wiki.debian.org/DebianEdu/Documentation/Manuals/Rosegarden)nuxaudio.org/wiki/abcmiditutorial)
+ * [AlsaModularSynth - Making a vocoder](http://wiki.linuxaudio.org/wiki/amsvocodertutorial)
+ * [Musescore tutorials](https://musescore.org/en/tutorials)
+ * [Screencasting with FFmpeg, jack_capture and Xephyr [Linux-Sound]](http://wiki.linuxaudio.org/wiki/screencasttutorial)
+ * [seq24: toggle sequences with a MIDI controller [Linux-Sound]](http://wiki.linuxaudio.org/wiki/seq24togglemiditutorial)
+ * trackers: [Mod Tracking Tutorial -- Introduction](http://files.byondhome.com/Audiophiles/iainperegrine.modtracker_tutorial/modtracking_tutorial_intro.html)
+ * trackers: [Mod Tracking Tutorial -- Introduction](http://files.byondhome.com/Audiophiles/iainperegrine.modtracker_tutorial/modtracking_tutorial_part1.html)
+ * trackers: [Trackers and Linux. || kuro5hin.org](https://www.kuro5hin.org/story/2002/6/8/2524/90038)
+ * trackers: [A Tutorial on Cutting Up a Breakbeat Using a Tracker || kuro5hin.org](https://www.kuro5hin.org/story/2005/11/13/182235/45)
+ * trackers: [.:: Milkytracker Tutorial ::.](http://www.seele07.de/milkytutorial/data/start_here.html)
+ * [Cover - Prodigy - Breath - LMMS - ZynAddSubFX - Linux - YouTube](http://www.youtube.com/watch?v=gxTxhv8H9X0)
+ * [Getting Started With SooperLooper on Vimeo](http://vimeo.com/7315051)
+ * [Introduction_to_Ardour_3.0_MIDI : Dan MacDonald : Free Download & Streaming : Internet Archive](http://www.archive.org/details/Introduction_to_Ardour_3.0_MIDI) * [01_PADsynth_strings.ogv - YouTube](http://www.youtube.com/watch?v=IA-7tpTfE-E)
+ * [Linux music tutorial: seq24, part 1 - YouTube](http://www.youtube.com/watch?v=J2WDHS1wYeM)
+ * [Seq24 Tutorial, Part 1 - setting loops - YouTube](http://www.youtube.com/watch?v=51W9PQfyMsg)
+ * [Amsynth - Linux Synthesizer - YouTube](http://www.youtube.com/watch?v=YHR9hQVrRIQ)
+ * [Ardour - Music Editing in Linux - Part #1 - YouTube](http://www.youtube.com/watch?v=43ES7p4ejX0)
+ * [You, Too Can Learn Renoise: Video Tutorial](http://createdigitalmusic.com/2009/10/you-too-can-learn-renoise-video-tutorial-from-dac-makes-you-a-tracker/)
+ * [Linux soft synth tutorial: part 6.1 - YouTube](http://www.youtube.com/watch?v=p6SoNX4bA1Y)
\ No newline at end of file