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"); + } + } + + push(@list,""); + foreach my $ins (@{$self->instruments()}) + { + push(@list,""); + while(my($key,$value)=each(%{$ins})) + { + unless(ref($value)) + { + push(@list,"<$key>$value"); + } + } + push(@list,""); + + push(@list,""); + while(my($key,$value)=each(%{$ins->{layer}})) + { + unless(ref($value)) + { + push(@list,"<$key>$value"); + } + } + 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"); + } + 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|^|)) + { + $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