Strus tutorial (with the MusicBrainz dataset)
Introduction
This is a tutorial we show how documents can be inserted into a strus index and how they can be queried with a command line tools. Unfortunately the part using other clients for query (like PHP) is not finished yet.
Dataset
We use the MusicBrainz dataset downloaded from MusicBrainz. The dataset is licenced under the terms described here. Thanks to MusicBrainz to make this dataset publicly available. The dataset contains a list of about 282'000 records with title, actor and id. The tutorial will show, how this data can be made retrievable with strus.
Prerequisites
This tutorial is based on
The following command runs the docker container with strus and nginx as webserver from the strus image with a terminal using bash:
Go
First step: Start the strus docker container in a shell
docker run -p 40080:80 -t -i patrickfrey/strus-ub1404-nginx:v0_2 /bin/bash
Second step: Download and prepare the data
If you want to skip this section, you can simply execute the script step_fetchData.sh.
This script will execute the steps described here in about a minute on a convenient developer machine.
Otherwise you have to manually fetch the data from MusicBrainz.
The dump of the record releases will be dumped and converted into a proprietary XML we will use.
Because there are many items to process, we will put up to 100 entries into one file. The analyzer
will be configured to handle one item of these 100 entries as one document.
Download and decompress the data with the following commands:
Prepare data directory
We create a directory for the data because the MusicBrainz files are all unpacked in current directory.
mkdir data cd data
Download and uppack the data dump
wget http://ftp.musicbrainz.org/pub/musicbrainz/data/fullexport/LATEST LATEST=`cat LATEST` wget http://ftp.musicbrainz.org/pub/musicbrainz/data/fullexport/$LATEST/mbdump-cdstubs.tar.bz2 bzip2 -d mbdump-cdstubs.tar.bz2 tar -xvf mbdump-cdstubs.tar
Create the documents
We count the maximum document identifier that will be assigned and we set $ndocs as the maximum number of documents we will get. In every document will be at maximum 100 items to insert. For every document identifier we create a document with the XML header.
cat mbdump/release_raw | sed 's/\&/\&/g' | sed 's/</\</g' | sed 's/>/\>/g' > dump.txt ndocs=`cat dump.txt | awk -F"\t" '{if(min=="")min=max=$1; if($1>max) {max=$1}; if($1< min) {min=$1}; } END {print int((max+1)/100)}'` mkdir doc idoc=0 while [ $idoc -lt $ndocs ] do echo "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\" ?>" > doc/$idoc.xml echo "<list>" >> doc/$idoc.xml idoc=`expr $idoc + 1` done
Fill the documents with the content of the dump
For every entry we want to process, we create an own XML tag. Every record is marked with an '<item>...</item>' tag.
cat dump.txt \ | awk -F"\t" '{FNAME=int($1/100); print "<item><id>" $1 "</id><title>" $2 "</title><artist>" $3 "</artist><date>" $4 "</date><upc>" $9 "</upc><note>" $10 "</note></item>" >> "doc/"FNAME".xml" }' idoc=0 while [ $idoc -lt $ndocs ] do echo "</list>" >> doc/$idoc.xml idoc=`expr $idoc + 1` done
Leave the data directory and inspect the result
We have now preprocessed the MusicBrainz example collection with about 282'000 records. We go back to the upper directory and inpect what we got. There should be about 3200 files and each file should contain about 100 items.
ls doc/We get:
0.xml 1267.xml 1536.xml 1805.xml 2074.xml 2343.xml 2612.xml 2882.xml 3150.xml 461.xml 730.xml 1.xml 1268.xml 1537.xml 1806.xml 2075.xml 2344.xml 2613.xml 2883.xml 3151.xml 462.xml 731.xml ... (more)
Here is an except of an example input file for strus. We can inspect the first 5 lines with:
head -n 7 doc/999.xml
We get:
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?> <list> <item><id>99929</id><title>Flutterstrut</title><artist>Hundred Year Flood</artist><date>2009-06-18 01:37:51.35028+00</date><upc>881387000018</upc><note>CD Baby id:hyf2</note></item> <item><id>99999</id><title>The Hanging Garden</title><artist>Hypnotique</artist><date>2009-06-18 01:37:52.548532+00</date><upc>634479159961</upc><note>CD Baby id:hypnotique</note></item> <item><id>99994</id><title>Hypnopilot</title><artist>Hypnopilot</artist><date>2009-06-18 01:37:52.49057+00</date><upc>088907200068</upc><note>CD Baby id:hypnopilot</note></item> <item><id>99995</id><title>The Sphinx of Imagination</title><artist>Hypnotica</artist><date>2009-06-18 01:37:52.503809+00</date><upc>634479143427</upc><note>CD Baby id:hypnotica</note></item>Every file created contains about 100 items to insert as document. The grouping of multiple items has been done to reduce the number of files created.
Third step: Create the storage
Initialize the storage database
Will will use the
We have one data element 'date' we would like to use for meta data restrictions.
The meta data used for restrictions are put into the meta data table. We can at any time alter the
meta data table or add new elements with the command strusAlterMetaData. But we can also define
some meta data within the strusCreate command.
The command to create the storage looks as follows:
strusCreate -s "path=storage; metadata=date UINT32"
The first argument describes the properties of the storage created with a semicolon-separated list of
configuration assigments. The path argument of it
defines the directory the key/value store database based on leveldb will write its files to.
The storage configuration string is dependend on the storage and key/value store database we use.
All following commands introduced in this tutorial later that refer to a storage will
have a configuration string parameter with the same syntax, but depending on the case
with different parameters.
With this call the storage has been created in the subdirectory 'storage'. Type
ls storage
and we will see (LevelDB files)
000005.log CURRENT LOCK LOG LOG.old MANIFEST-000004
Fourth step: Configure the document analyzer
We will use the standard document segmenter based on textwolf as we will process XML documents. The analyzer configuration describes, what elements will be extracted from the items and how items are defined. It also describes how these items are transformed into the form they are stored. The analyzer configuration defines different sections that describe what type of elements are created. For creating an analyzer configuration we create a file with the extension ".ana" and start to edit it:
touch tutorial.ana vi tutorial.ana
The following sub sections show what we define in this configuration file. The complete analyzer configuration is defined in tutorial.ana. The textwolf segmenter used in this tutorial defines XML selection expressions is the abbreviated syntax of XPath with 2 exceptions:
- Tag selections like "/list/item/title" do not select a subtree but just the tag.
- Contect selections like "/list/item/title::text()" are written as "/list/item/title()" (the "::text" is omitted).
Define the items to index (collection units aka documents)
As we saw in the preparation of the documents there are about 100 items inserted into one file. For separating these items as documents to insert we have to define a section document that defines what selection expression defines a document in an input file:
[Document] doc = /list/item;
Define the the search index (retrievable items)
The elements that are subject of retrieval are put into the section [SearchIndex].
We decide to make words in the tags "title","artist" and "note" retrievable.
All of them we tokenize the same way (as words) and we use the snowball stemmer ("stem")
followed by a lowercase conversion ("lc") and a diacritical character conversion ("convdia")
to normalize them.
Our language we instrument the stemmer and the diacritical characer
conversion with is English ("en").
The feature type name we assign to the produced search index features is for all features "word".
[SearchIndex] word = convdia(en):stem(en):lc word /list/item/title(); word = convdia(en):stem(en):lc word /list/item/artist(); word = convdia(en):stem(en):lc word /list/item/note();
Intuitively this configuration is understod if we read every feature assignment from right to left: Select an element, select the tokens from an element selection, normalize the tokens: "lc -> stem -> convdia" and assign them to a feature type "word".
Define the elements the forward index (for summarization)
The elements that are used for summarization are put into the section [ForwardIndex]. We decide to use the retrievable words in the tags "title","artist" and "note" also for summarization to show what query features matched in the document. Because the summary should show the original content of the document without markup, we use a whitespace tokenizer ("split") and no normalizer ("orig") to convert them for insert.
[ForwardIndex] orig = orig split /list/item/title(); orig = orig split /list/item/artist(); orig = orig split /list/item/note();
Intuitively this configuration is understod as the search index definition by reading the processing steps from right to left.
Define the attributes
Now assign some attributes to each document inserted. Attributes are used in the representation of the result. There is one reserved attribute name that is very important for our case. It is the attribute "docid". When defining it, the strusInsert program will define the content of this attribute as document identifier. If we do not define it, the input file path will be used as document identifier. Because the document identifier has to be unique, we have to create one when there are multiple documents grouped in one input file as we did before. For all four elements we will use a tokenization ("content") and normalization ("orig"), that keeps the elements in their original state:
[Attribute] docid = orig content /list/item/id(); title = orig content /list/item/title(); upc = orig content /list/item/upc(); note = orig content /list/item/note();
The definitions of the section attribute is read intuitively from right to left as the elements introduced before with the exception that the leftmost identifier specifies the name of the attribute the value is assigned to.
Define the meta data
As final step we assign the meta data element 'date' we want to use for restrictions but also in the representation of the result. For converting the date into a format we can use as meta data table element we use the 'date2int' normalizer. As start date of the date as integer calculation we chose the year of the invention of the phonograph by Thomas Edison and we define the unit as number of days since then. The input format we deduce by looking at an input sample (bravely assuming here that all dates have the same format):
2009-06-18 01:37:52.503809+00
The tokenization is just forwarding the tag content. Here is the configuration of the date:
[MetaData] date = date2int("d 1877-01-01", "%Y-%m-%d %H:%M:%s *") content /list/item/date();
The definitions of the section attribute is read intuitively from right to left as the elements introduced before with the exception that the leftmost identifier specifies the name of the meta data element the value is assigned to.
Check the configuration
The be sure that we got everything right, we now test what we got with one candidate input file. We can do this by calling the program strusAnalyze with our configuration and an input file:
strusAnalyze tutorial.ana doc/2333.xml | less
We have a look at the first document analyzed and see that all went well (Multi part document results of the strusAnalyze program are separated by '-- document ' followed by the identifier assigned to it in the [Document] section):
-- document doc search index terms: 1 word 'live' 2 word 'in' 3 word 'milan' 5 word '27' 6 word '9' 7 word '1956' 8 word 'maria' 9 word 'calla' forward index terms: 1 orig 'Live' 2 orig 'in' 3 orig 'Milan' 4 orig '(27,9,1956)' 8 orig 'Maria' 9 orig 'Callas' metadata: date '48527' attributes: docid '233372' title 'Live in Milan (27,9,1956)'
Check single steps of the analyzer configuration
What do we do when something went wrong when checking a document with strusAnalyze ?
Let's assume that our output was empty. There are tools to check the different steps of
document analysis. The first program is strusSegment
that allows us to check wheter a selection expression matched. Let's take the document title
as example:
strusSegment -e '/list/item/title()' doc/2333.xml | head -n 2The output looks as follows:
'Live in Milan (27,9,1956)' 'Greatest Hits'
This looks good as expected. The second program for checking analyzer steps is strusAnalyzePhrase. This program allows us to check tokenizers and normalizers used in the configuration. Let's check if words are tokenized and normalized correctly:
echo 'Live in Milan (27,9,1956)' | strusAnalyzePhrase -n "convdia(en):stem(en):lc" -t "word" -This leads to the following output:
1 'live' 2 'in' 3 'milan' 4 '27' 5 '9' 6 '1956'
At the first glance this looks good. But why are the positions assigned different compared with the analyzer run ? The word '27' has position 5 when passed through the analyzer with our configuration and 4 when calling strusAnalyzePhrase. The reason is that the positions are assigned globally to all elements of the configuration. Every by byte position in the document defined by any tokenizer configured is respected when assigning the word positions. The strusAnalyzePhrase does not have this information, so it assigns the positions ascending. Because we have added in the analyzer configuration a method that produces a token '(27,9,1956)' that has a byte position between "Milan" and "27", this token gets the word position 4. The token '27' gets the following position assigned, in our example 5. The reason for this is that the positions of all the features in the forward index and the search index have to be in sync to define their occurrence relation by their word position.
Fifth step: Insert the documents into the storage
We got now the analysis of the documents done. Now we would like to insert the documents
into the storage created. The program strusInsert
helps us to do that. It has several options to boost the insert process. It depends very
much on the collection and the hardware used which parameters are best.
If we know that all documents inserted are new, then we should specify the option '-n' ('--new').
In this case the inserter can assume that all files inserted are new and thus it can allocate
document numbers in ranges. Otherwise the storage issues an immediate write for every
new document inserted and this slows down the insert process dramatically because
we have only new documents now.
Another important option is '-c' ('--commit') that sets the number of documents inserted
within one transaction. Because out documents are very small (less than a dozen terms),
the default of 1000 is not very reasonable. Better is 50000.
Here is our insert command with the proposed options for this collection:
strusInsert -n -c 50000 -f 100 -s "path=storage" tutorial.ana doc
that does not use multiple inserter threads (default), commits an open transaction after 50000 documents inserted, preallocates document numbers in ranges and fills the storage in the folder 'storage' with XML documents analyzed with the configuration tutorial.ana. This should output the following 2 lines after roughly a minute or half a minute:
inserted 282291 documents (total 282291), 0 transactions open done
Sixth step: Inspect the storage
The following examples use the dump from 29th of June 2015. We might get different
results with another dump.
Now as we have inserted all documents we are curious to see if everything went right. There is a program strusInspect that lets us inspect some properties of the storage. We chose the document with the document id '10009'. First we find out what internal document number this docid got:
strusInspect -s "path=storage" docno 10009we get
123
Then we do some calls to inspect this document
strusInspect -s "path=storage" attribute docid 123 strusInspect -s "path=storage" content orig 123 strusInspect -s "path=storage" attribute title 123 strusInspect -s "path=storage" pos word danc 123 strusInspect -s "path=storage" ff word danc 123 strusInspect -s "path=storage" df word danc strusInspect -s "path=storage" metatable strusInspect -s "path=storage" metadata date 123
and we get
10009 Absolute Let's dance 13 Various Absolute Let's dance 13 4 1 1575 date UInt32 48253
If we want to see more at once we can also use the dumpers strusDumpStorage and strusDumpStatistics to get more info. But for understanding the storage dump there is knowledge about the internal block structure required. The programs strusCheckStorage and strusCheckInsert are also useful for performing some checks on the storage created.
It is important to know that the dump program strusDumpStorage does not output any data format that we can rely on or that we can import again. This dump is not for backup or for further processing.
Seventh step: Example query by command line
In this section we examine the possibility to issue queries by the command line tools.
The command line tool for query has 3 different languages defined.
One for analyzing query phrases that is similar to the analyzer configuration, one for
defining the query evaluation scheme
and one for the query.
This is quite a lot for a tutorial. We can alterntively skip this section and go to the
section explaining how to issue a query with PHP. The mechanisms there do not
involve any of the languages indroduced here.
For issuing a query by command line we use the program strusQuery.
Query phrase analyzer configuration
First we need to create an analyzer configuration for query phrases. We name the file query.ana:
word = convdia(en):stem(en):lc word;
It looks like an excerpt of the document analyzer configuration we wrote before. And this similarity is wished because we intent to normalize and name the query features in the same way as the document features, so that they match each other. As in the document analyzer configuration the lines are intuitively understood by reading them from right to left: Tokenize by selecting the words and normalize it by applying lower case, stemming with snowball and conversion of diacritical characters to get elements of the feature type word.
Query evaluation scheme configuration
In a second step we define a simple query evaluation scheme. We name the file of the query evaluation scheme query.eva:
SELECT qry; EVAL tf( .match=qry ); SUMMARIZE docid = attribute( name=docid ); SUMMARIZE title = attribute( name=title );
The language has only 4 commands. TERM to define structural terms that are not defined in
the query, but that are subject to query evaluation like for example sentence delimiters.
SELECT to define the set of query features that define what documents are weighted.
EVAL to define a query evaluation scheme to use. SUMMARIZE to define a summarization
step that is done to accumulate attributes of the documents presented as result.
One thing that might puzzle is the name 'qry' for the feature sets. Why not 'word'
as we indexed the terms in the query and in the document. Strus distinguishes between
term types, that classify a term and the feature set, that defines a name to address
a group of features. In this tutorial where we have just a simple document struture
and one list of query terms, this distinction might not be justified. But in more
complex cases where we have expressions involved, many levels of evaluation, a selection
of features that is different to the features weighted and a
summarization that works on different sets of features than weighting and selection,
we need another level on which elements of the query are grouped and addressed with.
This query evaluation scheme is read as follows:
Take the documents that contain any matches of the features of the set 'qry' and evaluate
the simple scheme 'tf' (count query features matching ~ feature weight * ff).
Show the document id and the title attribute of the document in the result.
Query source file
As third element we need a query. We name the file with the query query.qln:
[Feature qry :word] dance
A query has two type of sections [Feature] and [Condition]. The later one that defines restriction expressions on meta data is not used in our simple example query. The first one [Feature qry:word] defines an expression and assings it to the feature set 'qry', that is the feature set we referenced in query evaluation. The default phrase type used for analyze phrases is 'word'. This selects the method to be used to analyze a phrase that has not explicit specification of the phrase type. This is the case in our query that just contains one term 'dance'.
Issue the query on command line
For issuing our query, we call:
strusQuery -s "path=storage" query.ana query.eva query.qln
and we get our first search result with strus:
ranked list (starting with rank 0, maximum 10 results): [1] 25357 score 3 docid (124428) title (Dance Dance Dance) [2] 36620 score 3 docid (135366) title (Dance Dance Dance) [3] 199006 score 3 docid (313932) title (Dance - Dance - Dance) [4] 172756 score 3 docid (283564) title (Time for Dancing Ballroom Dance & Latin Ballroom Dance, Volume 4) [5] 255422 score 3 docid (73977) title (Dance, Dance, Dance) [6] 203579 score 3 docid (318916) title (Dance!) [7] 199597 score 3 docid (314534) title (Good Friends, a fifth selection of dances from Calculated Figures) [8] 152754 score 3 docid (259327) title (Russian Folk Dances of the Moiseyev Dance Company) [9] 2630 score 2 docid (102456) title (Israel Dances - Greatest Folk Dances) [10] 280052 score 2 docid (97811) title (Hannah & Mariah Dancing)
Eighth step: Example query with PHP
In this section we examine the possibility to issue queries in a scripting language. As example we use the PHP bindings to query our strus storage built. The PHP scripts running in the webserver context can not directly access the storage files. The strus PHP commands issued are using a proxy implementation of the strus interface that redirect the calls to be executed on a server. This server is implemented as program strusRpcServer. Because we started the image with a shell, the docker CMD starting the strus server was ignored. So we have to start it by hand in the background:
strusRpcServer -s "path=storage" &
Test execution chain
We start with a simple PHP script that asks the strus storage for the number of documents inserted. We call the script status.php:
<!DOCTYPE html> <html> <body> <?php require "strus.php"; try { // Create the strus context as proxy redirecting the // commands to the strus server listening on port 7181: $context = new StrusContext( "localhost:7181" ); // Create the storage client: $storage = $context->createStorageClient( "" ); echo '<p>'; // Print the number of documents inserted: echo "Number of documents inserted: " . $storage->nofDocumentsInserted() . "!"; echo '</p>'; } catch ( Exception $e) { // Handle possible error: echo '<p><font color="red">Caught exception: ' . "{$e->getMessage()}</font></p>"; } ?> </body> </html>
In order to make it accessible for nginx, we have to copy it to the web servers html pages root directory and give it the right permissions:
cp status.php /usr/share/nginx/html/ chown www-data:www-data /usr/share/nginx/html/status.php chmod +x /usr/share/nginx/html/status.php
Now we can call it via HTTP, say we can enter the following URL into a web browsers address bar. We have specified port 40080 when starting the strus docker container, so we also specify it here:
http://<IP_ADDRESS>:40080/status.php
and we get:
Issue a query
Now we try to create a simple page showing a ranked list of best matches to a query passed as GET parameters. We call the PHP source file search.php:
<!DOCTYPE html> <html> <body> <?php require "strus.php"; // The following helper function will be used to evaluate a query: function evalQuery( $queryString, $minRank, $maxNofRanks) { // We create the strus context as proxy to the strusRpcServer // started in our docker container listening on port 7181: $context = new StrusContext( "localhost:7181" ); // We create the storage proxy interface refering to the storage // served by the server by passing an empty storage configuration: $storage = $context->createStorageClient( "" ); // We create the analyzer proxy interface: $analyzer = $context->createQueryAnalyzer(); // We create the query evaluation proxy interface: $queryeval = $context->createQueryEval(); // Here we define how the query is analyzed. In our example we will introduce // only one phrase type and call it "qry". The produced feature type "word" matches // to the feature type in the search index of the document. The tokenizer used // is the same as for the features in the document ("word"). So is the normalizer // defined. Note that the execution of the normalizer is read from left to // right and not as in the analyzer configuration language from right to left. $analyzer->definePhraseType( "qry", "word"/*feat type*/, "word"/*tokenizer*/, ["lc", ["stem", "en"], ["convdia", "en"] ] /*normalizer*/); // Now we define our weighting function, that counts the number of query // feature matches in the document. Weighting function attribute names, // that reference features, start with a dot '.'. Others referencing // number or string constants not: $queryeval->addWeightingFunction( 1.0, "tf", [".match" => "qry" ]); // Next we define what is presented as attribute of the result. // We would like to see the document identifier and the document title: $queryeval->addSummarizer( "DOCID", "attribute", [ "name" => "docid" ] ); $queryeval->addSummarizer( "TITLE", "attribute", [ "name" => "title" ] ); // For the documents to match we have to define a selection expression. // The selected features will be addressed by the feature set identifier "sel": $queryeval->addSelectionFeature( "sel"); // Now we have finished the query evaluation scheme definition and we create // the query to issue. First we define the query object to instantiate: $query = $queryeval->createQuery( $storage); // Next we take the query string as phrase and analyze it. In a more complex // system we would parse the query language first and the elements of the // query language would be the queryphrases to analyze. In our simple example // setting, we have just a single phrase query: $terms = $analyzer->analyzePhrase( "qry", $queryString); if (count( $terms) > 0) { // Now we create the query expressions. The selection feature "sel" will // be represented as selection of documents that contain all query features. // The weighted features "qry" will be the set of terms in the query. // The query is constructed with operations on a stack: foreach ($terms as &$term) { $query->pushTerm( "word", $term->value); // ... The term pushed is now the topmost element on the stack $query->pushDuplicate(); // ... We push another instance of the term $query->defineFeature( "qry"); // ... One we take from the stack and create a feature // the other copy remains on the stack and will be used to create // the selection expression ("sel"). } $query->pushExpression( "contains", count($terms), 0); // ... all copied features (pushDuplicate) are taken from the stack and an // expression is constructed that matches all documents that // contain all the features. The expression is pushed on the stack. $query->defineFeature( "sel"); // ... Define the selection feature "sel" from the expression on the top of the stack. } // At last we define the number of documents to retrieve and the first rank to show: $query->setMaxNofRanks( $maxNofRanks); $query->setMinRank( $minRank); // And we evaluate the query and return it as result: return $query->evaluate(); } // Parse the query parameters: parse_str( getenv('QUERY_STRING'), $_GET); $queryString = $_GET['q']; $nofRanks = 20; $minRank = 0; if (array_key_exists( 'n', $_GET)) { $nofRanks = intval( $_GET['n']); } if (array_key_exists( 'i', $_GET)) { $minRank = intval( $_GET['i']); } // Call the defined subroutine to evaluate the query: $results = evalQuery( $queryString, $minRank, $nofRanks); // Display the result: echo '<table border=1>'; foreach ($results as &$result) { echo '<tr><td>' . $result->DOCID . '</td><td>' . $result->TITLE . '</td></tr>' . "\n"; } echo '</table>'; ?> </body> </html>
Now we copy the file into the webservers document area and set the permissions correctly:
cp search.php /usr/share/nginx/html/ chown www-data:www-data /usr/share/nginx/html/search.php chmod +x /usr/share/nginx/html/search.php
Now we can call the search page via HTTP, say we can enter the following URL into a web browsers address bar as we did it with the status.php page. We search for "party dance", and retrieve the first 10 ranks starting with the best match:
http://<IP_ADDRESS>:40080/search.php?q=party%20dance&i=0&n=10
and we get:
Ninth step: Think about build your own search application
This tutorial hopefully showed that it is simple to create a search engine for a simple data set. Unfortunately search applications are normally not simple at all. But if you have ideas or questions about the strus search engine, please contact us by mail.


This is hard to explain, but here it goes. I've been a long time suffer of depression/anxiety for about 15 years now. Although I've done tones of hallucinogens, up until last year I was only smoking dope and tripping on cactus & M'Gs, hadnít had dope, shrooms or acid for years. But, I stopped it - I am getting on a bit. Turned to alcohol, as it was the acceptable norm - but has landed me in loads of bother. I've also been going through a reflective phase of my life as to why I am who I am etc. Have been experiencing a lot of deep routed problems and emotions over the last year - and problems with certain individuals...blah blah... you get the drift.
Anyway, life has been rotten lately and thinking about suicide a lot. Anyway, last Thursday, due to a certain amount of problems and people, I decided on an overdose (This has been my 4th attempt).I'm not going to explain the events of what made me do it, but have just got to explain how far gone I was.
I ended up in Hospital - unconscious at the time. But, I started to come around, floating in and out of consciousness when they where fixing me up with all the wonders of ECG's, drips and callipers (oh, painfull!!!). The 1st memories I had were lying in the bed, surrounded by specialists etc. I felt as though they were dissecting me, as though I was dead.  I kept going back under .At one point, I awoke and was surrounded by people I knew years ago, people I knew when I was as young as five. One of my old friends was sat by my bedside, holding my hand and reassuring me. I never heard, or recall words spoken - it just seemed so peaceful and safe.
Although this only went on for a matter of a few hours in reality, it seemed that days, sometimes weeks where passing by. One time I awoke and the whole thing went sickening and thought I was in Hell and in damnation - or close to it. It was total Hellraiser. The walls opened up, people were ripped apart. Screaming and the most demonic laughter was going on. I saw my friend hanging from a rope with the most sad expression on his face. There is so much detail in it, but I'm cutting it short, because it is still effecting me so intensely now. I took 14 x 50 mg of Amitriptyline, and had also had a fair bit to drink beforehand.
I am just so glad to be alive - I cannot explain how profound and deeply beautiful it was at times, but this was also a time of the utmost horror. Somebody, or something somewhere has given me a 2nd chance! I was out of Intensive care and moved to a normal ward and have been out for two days now. The only way I can compare this is having Datura/Deadly Nightshade and timing it by 100. I thank everyone at the Hospital, my family and some external force that is far beyond our comprehension.
Experience Reports are the writings and opinions of the individual authors who submit them.
Some of the activities described are dangerous and/or illegal and none are recommended by Erowid.


| Interface | Description |
|---|---|
| TestInterface1 |
Test interface #1 extending Serializable one.
|
| TestInterface2 |
Test interface #2 extending Serializable one.
|
| TestInterface3 |
Test interface #3 extending Serializable one.
|
| TestInterface4 |
Test interface #4 extending Serializable one.
|
| ThrowingRE |
Interface showing that checked method of this class will throw
RemoteExceptions.
|
| Class | Description |
|---|---|
| AbstractTestBase |
Base class for proxytrust spec-tests.
|
| BaseExporter |
Base for all used exporter classes.
|
| BaseIsTrustedObjectClass |
Base class for auxiliary classes for testing 'isTrustedObject' method of
ProxyTrustVerifier class.
|
| BaseProxyTrust |
Base class for all ProxyTrust classes implementing RemoteMethodControl and
ProxyTrust interfaces.
|
| BaseTrustVerifier |
Base class for all TrustVerifier-s implementing TrustVerifier interface.
|
| BaseTrustVerifierContext |
Base class for all TrustVerifierContext classes implementing
TrustVerifier.Context interface having constructor with 1 parameter:
Object[].
|
| FalseProxyTrust |
Class implementing RemoteMethodControl and ProxyTrust interfaces whose
'getProxyVerifier' method returns FalseTrustVerifier instance.
|
| FalseTrustVerifier |
Class implementing TrustVerifier interface whose 'isTrustedObject' method
always returns false.
|
| IHandlerThrowingRE |
Invocation handler whose 'getProxyTrustIterator' method returns
TrustIteratorThrowingRE.
|
| IHandlerThrowingSE |
Invocation handler whose 'getProxyTrustIterator' method always throws
SecurityException.
|
| Interface12Impl |
Class implementing test interfaces #1 and #2.
|
| Interface12TEImpl |
Class implementing TrustEquivalence, TestInterface1 and TestInterface2
interfaces.
|
| Interface13Impl |
Class implementing test interfaces #1 and #3.
|
| Interface1Impl |
Class implementing test interface #1.
|
| Interface1RMCTEImpl |
Remote class implementing RemoteMethodControl, TrustEquivalence and
TestInterface1 interfaces.
|
| Interface1TEImpl |
Class implementing TrustEquivalence and TestInterface1 interfaces.
|
| Interface21Impl |
Class implementing test interfaces #2 and #1.
|
| Interface2Impl |
Class implementing test interface #2.
|
| Interface2RMCPTTEImpl |
Class implementing RemoteMethodControl, ProxyTrust, TrustEquivalence and
TestInterface2 interfaces.
|
| Interface2TEImpl |
Class implementing TrustEquivalence and TestInterface2 interfaces.
|
| Interface3RMCTEImpl |
Class implementing RemoteMethodControl, TrustEquivalence and TestInterface3.
|
| Interface4RMCPTTEImpl |
Class implementing RemoteMethodControl, ProxyTrust, TrustEquivalence and
TestInterface4 interfaces.
|
| InvHandler |
Test invocation handler for creating proxy classes.
|
| NonProxyObjectThrowingRE |
Class whose 'getProxyTrustIterator' method returns TrustIteratorThrowingRE.
|
| NonProxyObjectThrowingSE |
Class whose 'getProxyTrustIterator' method always throws SecurityException.
|
| NonProxyStaticMethodObject |
Class having static method 'ProxyTrustIterator getProxyTrustIterator()' and
which is not a dynamic proxy class.
|
| NonProxyWrongMethodObject |
Class having public method
'ProxyTrustIterator getProxyTrustIterator() throws FakeException'
and which is not a dynamic proxy class
|
| NonRMCProxyTrust |
Class implementing ProxyTrust but is not implementing RemoteMethodControl
interface whose 'getProxyVerifier' method returns TrueTrustVerifier instance.
|
| NullProxyTrust |
Class implementing RemoteMethodControl and ProxyTrust interfaces whose
'getProxyVerifier' method returns null instance.
|
| ProxyTrustThrowingRE1 |
Class implementing ProxyTrust and RemoteMethodControl interfaces whose
'getProxyVerifier' method always throws RemoteException.
|
| ProxyTrustThrowingRE2 |
Class implementing RemoteMethodControl and ProxyTrust interfaces whose
'getProxyVerifier' method returns TrustVerifierThrowingRE instance.
|
| ProxyTrustThrowingSE1 |
Class implementing ProxyTrust and RemoteMethodControl interfaces whose
'getProxyVerifier' method always throws SecurityException.
|
| ProxyTrustThrowingSE2 |
Class implementing RemoteMethodControl and ProxyTrust interfaces whose
'getProxyVerifier' method returns TrustVerifierThrowingSE instance.
|
| ProxyTrustUtil |
Utility class containing different static methods.
|
| PTTEImpl |
Serializable class implementing ProxyTrust and TrustEquivalence interfaces.
|
| RMCImpl |
Remote serializable class implementing RemoteMethodControl
|
| RMCPTImpl |
Remote serializable class implementing RemoteMethodControl and ProxyTrust
interfaces.
|
| RMCPTTEImpl |
Remote serializable class implementing RemoteMethodControl, ProxyTrust and
TrustEquivalence interfaces.
|
| RMCPTTEReadPTTE |
Class implementing Serializable, ProxyTrust, RemoteMethodControl and
TrustEquivalence interfaces, having 'readResolve' which returns
instance of the class implementing only ProxyTrust and TrustEquivalence
interfaces.
|
| RMCPTTEReadRMCPT |
Class implementing Serializable, ProxyTrust, RemoteMethodControl and
TrustEquivalence interfaces, having 'readResolve' which returns
instance of the class implementing only RemoteMethodControl and
ProxyTrust interfaces.
|
| RMCTEImpl |
Remote serializable class implementing RemoteMethodControl and
TrustEquivalence interfaces.
|
| RMCTEReadRMC |
Class implementing Serializable, RemoteMethodControl and TrustEquivalence
interfaces, having 'readResolve' which returns instance of the class
implementing only TrustEquivalence interface.
|
| SPTRemoteObject |
Remote Object implementing ServerProxyTrust interface so it could be used
as parameter for 'export' method invocation for ProxyTrustExporter.
|
| StaticMethodIHandler |
Invocation handler having static method
'ProxyTrustIterator getProxyTrustIterator()'.
|
| TEImpl |
Serializable class implementing TrustEquivalence interface.
|
| TestClassLoader |
Test Class Loader to create proxies.
|
| TestTrustIterator |
Class implementing ProxyTrustIterator interface having constructor with 1
paramter: Object[], whose 'next' method returns objects specified in
constructor's parameter in the same sequence (i.e. obj[0] first,
then obj[1] ... etc.)
|
| TrueProxyTrust |
Class implementing RemoteMethodControl and ProxyTrust interfaces whose
'getProxyVerifier' method returns TrueTrustVerifier instance.
|
| TrueTrustVerifier |
Class implementing TrustVerifier interface whose 'isTrustedObject' method
always returns true.
|
| TrueTrustVerifierContext |
Class implementing TrustVerifier.Context interface having constructor with 1
parameter: Object[].
|
| TrustIteratorThrowingRE |
Class implementing ProxyTrustIterator interface whose 'next' method always
throws RemoteException.
|
| TrustVerifierContext |
Class implementing TrustVerifier.Context interface having constructor with 1
parameter: Object[].
|
| TrustVerifierContextThrowingRE |
Class implementing TrustVerifier.Context interface having constructor with 1
parameter: Collection.
|
| TrustVerifierContextThrowingSE |
Class implementing TrustVerifier.Context interface having constructor with 1
parameter: Collection.
|
| TrustVerifierThrowingRE |
Class implementing TrustVerifier interface whose 'isTrustedObject' method
always throws RemoteException.
|
| TrustVerifierThrowingSE |
Class implementing TrustVerifier interface whose 'isTrustedObject' method
always throws SecurityException.
|
| UnexportSameExporter |
Class implementing Exporter interface whose unexport method returns the same
value as parameter specified
|
| UnexportTrueExporter |
Class implementing Exporter interface whose unexport method always returns
true.
|
| ValidBootExporter |
Class implementing Exporter interface to be used as bootExporter
for ProxyTrustExporter.
|
| ValidIHandler |
Invocation handler having constructor with 1 parameter: Object[]
and having public method 'ProxyTrustIterator getProxyTrustIterator()'
returning TrustIterator with objects specified in constructor's parameter.
|
| ValidMainExporter |
Class implementing Exporter interface to be used as mainExporter
for ProxyTrustExporter.
|
| ValidNonProxyObject |
Class having constructor with 1 parameter: Object[] and public method
'ProxyTrustIterator getProxyTrustIterator()' returning TestTrustIterator
with objects specified in constructor's parameter.
|
| WrongMethodIHandler |
Invocation handler having public method
'ProxyTrustIterator getProxyTrustIterator() throws FakeException'.
|
| Exception | Description |
|---|---|
| FakeException |
Test Exception class for tests checking throwing exceptions.
|
Copyright 2007-2013, multiple authors.
Licensed under the Apache License, Version 2.0, see the NOTICE file for attributions.


Grid based design is The Renaissance artists of 15th and 16th Centuries used divine proportion to create their paintings, architecture or sculptures. In the similar fashion the designers today use this ratio while developing posters, layouts and brochures. Designers are of the opinion that instead of relying simply on artistic ideas, divine proportion is more useful in giving logical guidelines for developing layouts which are not only attractive but has unique appeal. One way to apply grid based design is by splitting your designs into a row the full length of the screen width, and 12 columns using percentages so they can stay in proportion on different sized screens. 12 columns is the perfect number to break up into two, three or four columns.
Responsive webpages are pretty hyped up, as we access web pages on many different devices and screens including smart phones, tablets, lap tops and desktops. Having a responsive page is really important for the user experience, so they can access an aesthetically pleasing design and page no matter which device they're using. One of the parts I benefitted from the most in the Udacity tutorial was the @ media tags, so your site can be responsive to the screen size its displayed on.
Creating a web page with semantic structure has long been considered a best practice because it gives the web page more meaning. In HTML4 and XHTML, web designers can create a semantic structure by following well-established rules with HTML tags.
For example, the most important headline on a page should be formatted with the Heading 1, or < h1 > tag. Similarly, most web designers now define class or ID styles for the main sections of a site with names like header and footer to identify their role and importance on a page.
HTML5 takes this concept a step further by adding a series of new semantic elements. Rather than create a class or ID style and apply it to a < div > tag, in HTML5 you can simply use tags with names like < header > and < footer >.
Thus, rather than use ID styles such as < div id=”nav”>, you can simply use < nav>. Much like < div> tags, however, none of the new tags does anything unless you define a set of style rules that correspond to the name.
Building these semantic elements into HTML5 gives designers a more consistent way of structuring HTML pages and is recommended because it makes web pages more accessible for anyone who uses a screen reader to read web pages aloud, as well as for search bots and other programs that need to interpret the content of a web page irrespective of its design.
Think of it this way: When you see a web page, you can generally tell by the design which part of the page is the header (it has the navigation area with links) and which is the footer (with the copyright and other details). But none of the bots, readers, and other programs that need to interpret web pages can “see” the design, and that’s part of the reason that using semantics is important.
The semantic structure of a page can be independent from the styling and positioning of the content, so you can create a page that has meaning because recognizable sections make up a logical structure.
I haven't seen the next tutorial on Bootstrap, but that looks very interesting. Some parts of CSS remind me of maths/algebra, especially giving columns percentages and the structure of writing it reminds me of algebra equations. Random thought! I also really enjoyed the kitten and puppy place holders, that you can give your required dimenions and it gives a perfect sized kitten/puppy photo to use as a place holder. I will definately be looking to incorporate those where possible.


An expat is a global citizen, a cosmopolitan, an international nomad. While it's traditionally been used to describe people that live and thrive in countries that are not their own or their parents', we use the term loosely to encompass anybody that's keen to push their boundaries beyond that of their own cultures and languages. If you're reading this, you've already taken a step towards that-- and so you're probably an expat.
We exist to foster an environment and community that shares an appreciation for diversity and unwavering global curiosity. We aim to support the world’s next global thinkers as they continue to inspire and change the world together. We believe that an international understanding comes through experience and exposure, and so by providing a platform to nurture and share our own experiences and perspectives, we hope to build human connections and knowledge bases that span cities, countries, and continents alike.
International, urbanite, global nomad, mobile citizen, curious, expatriate. If you embody any or all of these qualities, we think that you’ll be a great addition to our community. We strive to continue to push the boundaries of cultural understanding and global perspective, and anyone that’s willing to contribute to that future is welcome in our book.
Membership is open. As a General Member, you're invited to all our events; whether they be our student success panels, daytrips, Explore Seattle outings, or kickbacks alike. If you'd like to share your perspective even more, we'd love it if you became an Ambassador.
DubEx Ambassadors are a set of members that are particularly passionate about our cause. DubEx exists to support you– our family, and so by putting you in a leadership role as we run our events and nurture our community, we can better tailor the experience to you.
Nope. However, to keep our events running, we'd love it if you could support us with the occasional donation.


| com.episode6.hackit.mockspresso.Mockspresso.Builder (extensions in package com.episode6.hackit.mockspresso.easymock.powermock) | |
| com.episode6.hackit.mockspresso.easymock.powermock.EasyPowerMockFieldPreparer |
A FieldPreparer for Powermock + EasyMock. Powermock doesn't seem to offer a nice way to init mocks on an arbitrary object that isn't the testClass (and the runner/rule deals with those). So to handle testResources, we use this custom FieldPreparer that expects some mocks/spies to already be initialized (and doesn't touch those). We use |
| com.episode6.hackit.mockspresso.easymock.powermock.EasyPowerMockMockerConfig |
A MockerConfig for PowerMock + EasyMock |
| com.episode6.hackit.mockspresso.easymock.powermock.EasyPowerMockMockMaker |
A MockMaker for PowerMock + EasyMock |
| com.episode6.hackit.mockspresso.easymock.powermock.EasyPowerMockPlugin |
Plugin that applies |
| com.episode6.hackit.mockspresso.easymock.powermock.EasyPowerMockRulePlugin |
Plugin that applies |
| com.episode6.hackit.mockspresso.easymock.powermock.MockspressoEasyPowerMockPluginsJavaSupport |
Expose the extension methods defined here as MockspressoPlugins for consumption by java tests |


by Eli Golding /<EMAIL_ADDRESS>/ @eli_golding
Individual local development machines
install web server (apache/nginx)...
install PHP...
install MySQL...
install Redis, Memcache, NPM, RVM...
configure all the things...
get the code...
attempt to serve the application...
configure some more things to get it to work...
Dev #1
|
Dev #2
|
Dev #3
|
Dev #4
|
Can only have one setup
Inflexible infrastructure
Potential to cause problems with main OS
Port conflicts
Different type of machine than production
“Virtualization, in computing, refers to the act of creating a virtual (rather than actual) version of something, including but not limited to a virtual computer hardware platform, operating system (OS), storage device, or computer network resources.”
Vagrant is a command line tool that fully controls the creation and provisioning of virtual machines.
Everything is configured in a single file (VagrantFile) that part of the project.
Can be viewed as a wrapper around the VM provider and the provisioner.
The VagrantFile is checked into version control, allowing for great portability.
Vagrant.configure(2) do |config|
config.vm.box = "ubuntu/trusty64"
config.vm.network "forwarded_port", guest: 80, host: 8080
config.vm.network "private_network", ip: "<IP_ADDRESS>"
config.vm.synced_folder ".", "/var/www"
config.vm.provision "shell", inline: <<-SHELL
sudo apt-get install -y apache2
SHELL
end
config.vm.box = "ubuntu/trusty64"
Configures the base box of the machine. Value should be the name of an installed box or a shorthand name of a box in HashiCorp's Atlas.
config.vm.network "forwarded_port", guest: 80, host: 8080
config.vm.network "private_network", ip: "<IP_ADDRESS>"
Configures networks on the machine.
config.vm.synced_folder ".", "/var/www"
Synced folders enable Vagrant to sync a folder on the host machine to the guest machine, allowing you to continue working on your project's files on your host machine.
config.vm.provision "shell", inline: <<-SHELL
sudo apt-get install -y apache2
SHELL
Set the Provisioners that will automatically install software, alter configurations, and more on the machine as part of the vagrant up process.
Represents a piece of your infrastructure and it's desired state.
execute "composer-install" do
cwd '/var/www/app'
command "composer install"
end
directory "/vagrant/app/cache" do
owner "vagrant"
group "vagrant"
mode 0777
action :create
end
link "/etc/nginx/sites-enabled/my-cool-site" do
to "/etc/nginx/sites-available/my-cool-site"
end
execute "apt-get update" do
command "apt-get update"
end
apt_package "curl" do
action :install
end
php_packages = ['php5-cli','php5-curl','php5-mysql']
php_packages.each do |p|
apt_package p do
action :install
end
end
template "/etc/nginx/sites-available/my-awesome-site" do
source "vhost.erb"
variables({
:doc_root => '/var/www',
:server_name => 'vagrant.rox'
})
action :create
notifies :restart, resources(:service => "nginx")
end
server {
listen 80;
root <%= @doc_root %>;
index index.html index.php;
server_name <%= @server_name %>;
location / {
try_files $uri $uri/ /index.php;
}
error_page 404 /404.html;
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/www;
}
location ~ \.php$ {
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass unix:/var/run/php5-fpm.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
}
Configuration file that describes resources and their desired state.
Recipes are stored in cookbooks.
Cookbook contain recipes, template, files, custom resources, etc.
"If you want to increase innovation, you have to lower the cost of failure."


This repository is no longer maintained.
To contribute to the quickstarts, see the WildFly quickstarts. That repository contains the JBoss EAP quickstarts that are currently under development.
You can also download the JBoss EAP quickstarts from the Red Hat Developer web site.


A broad network of partners supports the Bio-Bridge Initiative efforts in facilitating and supporting technical and scientific cooperation for the conservation and sustainable use of biodiversity. Partner institutions help identify relevant sources of expertise and technical resources to meet enable Parties to meet their needs and identify technical and scientific cooperation opportunities. The Bio-Bridge Initiative complements existing initiatives and collaborates with partners to provide coordinated technical and scientific cooperation support, building on knowledge accumulated through these initiatives.
If you would like to know more about becoming a partner with the Bio-Bridge Initiative, please email us at<EMAIL_ADDRESS>Two external partners were contracted to support the CBD Secretariat to design and develop the Bio-Bridge Initiative, namely Quebec Centre for Biodiversity Science and the UNEP World Conservation Monitoring Centre. They coordinated the development of the Bio-Bridge Action Plan in partnership with the CBD Secretariat and contributed to the Bio-Bridge outreach activities.
Members of the Consortium of Scientific Partners on Biodiversity (CSP) were also consulted and are actively involved in the operational phase. Partnerships will be further expanded to ensure equitable representation of relevant regional institutions as well.


RPG World Builder
Do you have ideas for role-playing game worlds and campaigns, but wonder if you can turn them into something you can really play with your friends?
Are you an experienced GM, who has run a few different rules systems?
If you answered yes to both questions above, this site is for you. The tool on the next page will help you develop an idea you love into a successful campaign.
This isn't about rules. You already have books full of rules. You can get free, universal systems online.
The hard part is to get campaign ideas which are both:
- New, creative, and awesome
- Practical and playable
This tool starts with your ideas which are new, creative, and awesome and helps with the practical and playable part. That way, you can go wild with whatever idea you would love to run, with confidence that you will be able to write and run weeks of enjoyable adventures for your friends.
You can use the tool to develop as many ideas as you want, and create an account to save your work. You can also see all the campaigns created by other users. And you can clone them to modify or extend their ideas.
And please note that anyone else can see, use, and be inspired by your ideas, too.
 Or, read on...
First, I want to be 100% clear that this is a safety net, not a straitjacket. Feel free to ignore any part that does't fit your own idea.
That said, the tool asks questions designed to ensure that the campaign world...
- Has life and activity beyond the PCs.
- Includes fun details that apply to actual gameplay.
- Has reliable sources of adventure "hooks".
I've called the sources of adventure hooks "drivers". They are patterns that have worked in other games. And their goal is to ensure that you can always sit down and write a fun adventure, even without any specific new inspiration. (We've all been there.)
After you've filled out the form, I suggest saving it and coming back to it later. Then look at each answer, and ask yourself, "is this just kind of normal and average?" And if it is, change it. :-)
Once you are really happy with a campaign idea in this system, click "printable view" to open a simple text version of your work in another tab, and print it and/or save it. You're done with this system and ready to carry on developing the campaign by your usual methods with a notebook, word processor, index cards, or whatever you prefer.
Good luck and have fun!


Purple Martians
Technical Code Descriptions
Sound
Overview
Sound setup
Mixer volume controls
Theme music
Sound effects
Sample delay
Processing the hiss hound
Overview
Sound is one of the easiest and most straight forward things that I do in the game.
- I create a voice (main output)
- Then a main mixer to attach to the voice
- Then two other mixers to attach to the main mixer
- se_mixer is for the sound effects
- st_mixer is for the sound track
This is done so I can control the volume of the soundtrack and sound effects independantly.
I then attach samples, sample instances, and streams to these mixers.
Sound setup
These are the variables used:
// mwSound.h
class mwSound
{
public:
ALLEGRO_VOICE *voice = NULL;
ALLEGRO_MIXER *mn_mixer = NULL;
ALLEGRO_MIXER *se_mixer = NULL;
ALLEGRO_MIXER *st_mixer = NULL;
ALLEGRO_SAMPLE *snd[20];
ALLEGRO_SAMPLE_INSTANCE *sid_hiss;
ALLEGRO_AUDIO_STREAM *pm_theme_stream;
int fuse_loop_playing;
int sample_delay[8];
int se_scaler=5;
int st_scaler=5;
int lit_item;
int sound_on = 1;
void start_music(int resume);
void stop_sound(void);
void proc_sound(void);
void load_sound(void);
void set_se_scaler(void);
void set_st_scaler(void);
};
extern mwSound mSound;
// these variables are saved in the config file:
mSound.sound_on=1
mSound.se_scaler=2
mSound.st_scaler=1
In initial_setup() I call mSound.load_sound();
void mwSound::load_sound() // load sound driver and samples
{
int err = 0;
if (sound_on)
{
if(!al_install_audio())
{
mInput.m_err("Failed to initialize audio.\n");
err = 1;
}
if ((!err) && (!al_init_acodec_addon()))
{
mInput.m_err("Failed to initialize audio codecs.\n");
err = 1;
}
if (err)
{
sound_on = 0;
mConfig.save();
}
else
{
voice = al_create_voice(44100, ALLEGRO_AUDIO_DEPTH_INT16, ALLEGRO_CHANNEL_CONF_2);
if (voice == NULL) mInput.m_err("Failed to create voice.\n");
al_set_default_voice(voice);
mn_mixer = al_create_mixer(44100, ALLEGRO_AUDIO_DEPTH_FLOAT32, ALLEGRO_CHANNEL_CONF_2);
if (mn_mixer == NULL) mInput.m_err("Failed to create mn_mixer\n");
se_mixer = al_create_mixer(44100, ALLEGRO_AUDIO_DEPTH_FLOAT32, ALLEGRO_CHANNEL_CONF_2);
if (se_mixer == NULL) mInput.m_err("Failed to create se_mixer\n");
st_mixer = al_create_mixer(44100, ALLEGRO_AUDIO_DEPTH_FLOAT32, ALLEGRO_CHANNEL_CONF_2);
if (st_mixer == NULL) mInput.m_err("Failed to create st_mixer\n");
if (!al_attach_mixer_to_voice(mn_mixer, voice)) mInput.m_err("Failed attaching mn_mixer\n");
if (!al_attach_mixer_to_mixer(se_mixer, mn_mixer)) mInput.m_err("Failed attaching se_mixer\n");
if (!al_attach_mixer_to_mixer(st_mixer, mn_mixer)) mInput.m_err("Failed attaching st_mixer\n");
al_set_default_mixer(se_mixer);
al_reserve_samples(20);
char fn[20] = "snd/snd00.wav";
for (int x=0; x<10; x++)
{
fn[8] = 48 + x;
snd[x] = al_load_sample(fn);
}
sid_hiss = al_create_sample_instance(snd[3]);
al_set_sample_instance_playmode(sid_hiss, ALLEGRO_PLAYMODE_LOOP);
al_attach_sample_instance_to_mixer(sid_hiss, se_mixer);
pm_theme_stream = al_load_audio_stream("snd/pm.wav", 8, 1024);
if (pm_theme_stream == NULL) mInput.m_err("Error loading snd/pm.wav\n");
al_set_audio_stream_playmode(pm_theme_stream, ALLEGRO_PLAYMODE_LOOP);
al_set_audio_stream_playing(pm_theme_stream, 0);
al_attach_audio_stream_to_mixer(pm_theme_stream, st_mixer);
set_se_scaler();
set_st_scaler();
}
}
}
Mixer volume controls
I set the volume of se_mixer and st_mixer with se_scaler and st_scaler.
se_scaler and st_scaler are integers that range from 0-9.
They are set by the user in 'Settings' -> 'Main' and are also saved in the config file.
When they are set or changed, these functions apply the values to the mixers:
void mwSound::set_se_scaler(void)
{
if (sound_on) al_set_mixer_gain(se_mixer, (float)se_scaler / 9);
mConfig.save();
}
void mwSound::set_st_scaler(void)
{
if (sound_on) al_set_mixer_gain(st_mixer, (float)st_scaler / 9);
mConfig.save();
}
Theme music
The theme music is started like this when a game is started or resumed.
The only difference is that resume does not rewind before starting.
void mwSound::start_music(int resume)
{
if (sound_on)
{
al_set_mixer_gain(st_mixer, (float)st_scaler / 9);
al_set_mixer_gain(se_mixer, (float)se_scaler / 9);
// reset sound counters
for (int c=0; c<8; c++) sample_delay[c] = mLoop.frame_num;
if (sound_on)
{
if (!resume) al_rewind_audio_stream(pm_theme_stream);
al_set_audio_stream_playing(pm_theme_stream, 1);
}
}
}
Theme music and sound effects are stopped like this when the game is exited:
void mwSound::stop_sound(void)
{
if (sound_on)
{
al_set_audio_stream_playing(pm_theme_stream, 0);
al_set_sample_instance_playing(sid_hiss, 0);
}
}
Sound effects
When an event in the game requires a sound to be played, here is how I do that:
void mwGameEvent::add(int ev, int x, int y, int z1, int z2, int z3, int z4)
{
...
if (mSound.sound_on)
{
/* sample numbers
0 - player shoots
1 - d'OH
2 - bonus
3 - hiss
4 - la dee dah door, key, exit
5 - explosion
6 - grunt 1 shot
7 - grunt 2 hit
8 - enemy killed */
switch (ev)
{
// al_play_sample(ALLEGRO_SAMPLE *spl, float gain, float pan, float speed, ALLEGRO_PLAYMODE loop, ALLEGRO_SAMPLE_ID *ret_id)
case 1: // player shoots
//al_play_sample(snd[0], 0.71, 0, .8, ALLEGRO_PLAYMODE_ONCE, NULL);
al_play_sample(mSound.snd[0], 0.81, 0, .7, ALLEGRO_PLAYMODE_ONCE, NULL);
break;
case 2: // explosion
al_play_sample(mSound.snd[5], .78, 0, 1, ALLEGRO_PLAYMODE_ONCE, NULL);
break;
case 20: case 22: case 4: // la dee dah (key, door, unlocked exit, warp)
if (mSound.sample_delay[4]+30 < mLoop.frame_num)
{
mSound.sample_delay[4] = mLoop.frame_num;
al_play_sample(mSound.snd[4], 0.78, 0, 1, ALLEGRO_PLAYMODE_ONCE, NULL);
}
break;
case 24: // sproingy
al_play_sample(mSound.snd[9], 1, 0, 1, ALLEGRO_PLAYMODE_ONCE, NULL);
break;
case 27: // purple coin
if (mSound.sample_delay[2]+30 < mLoop.frame_num)
{
mSound.sample_delay[2] = mLoop.frame_num;
al_play_sample(mSound.snd[2], 0.78, 0, 1, ALLEGRO_PLAYMODE_ONCE, NULL);
}
break;
case 40: case 41: // player got hurt
al_play_sample(mSound.snd[6], 0.5, 0, 1, ALLEGRO_PLAYMODE_ONCE, NULL);
break;
case 12: // player took damage
if (mSound.sample_delay[7]+14 < mLoop.frame_num)
{
mSound.sample_delay[7] = mLoop.frame_num;
al_play_sample(mSound.snd[7], 0.5, 0, 1, ALLEGRO_PLAYMODE_ONCE, NULL);
}
break;
case 42: // enemy killed
al_play_sample(mSound.snd[8], 0.5, 0, 1.2, ALLEGRO_PLAYMODE_ONCE, NULL);
break;
case 8: // d'Oh (player died)
al_play_sample(mSound.snd[1], 1, 0, 1, ALLEGRO_PLAYMODE_ONCE, NULL);
break;
}
Sample delay
I have implemented a simple system to prevent re-triggering a sample while it is still playing.
int sample_delay[20] keeps track of the frame_num when a sample was played.
Won't allow the same sample to be re-played until a specified number of frames has passed.
Processing the hiss hound
To process the hiss sound (when a bomb fuse is burning or a rocket is lit), I call this function once per frame:
void mwSound::proc_sound() // called once per frame
{
if (sound_on)
{
if (lit_item) al_set_sample_instance_playing(sid_hiss, 1);
else al_set_sample_instance_playing(sid_hiss, 0);
lit_item = 0;
}
}
lit_item is set to 0 at the end of this function.
When bombs and rockets are processed, they will set lit_item if they are lit.


Credentials to use when writing (optional).
Credentials are only required if your InfluxDB requires a user and password for writing.


The scope of these tests is to have a fair comparison of different methods of serving CSS content. The use or absence of third-party CSS libraries, build tools, minification...etc is beyond the scope of these tests.
critical.css
and bottom.css
.
Inline critical CSS and asynchronously load remaining CSS using media="print" trick
Test Results: WPT
Inline critical CSS. preload
and asynchronously load
remaining CSS using media="print" trick
Test Results: WPT
Inline all CSS, total document size: 139KB (compressed)
Test Results: WPT
Connect to external stylesheets in the <head>
.
Test Results: WPT
preload
to the external stylesheets and load them in
the <head>
.
Test Results: WPT


Mason Rudolph avoids suspension in Myles Garrett incident, leaving some NFL fans upset
by Jordan HeckMason Rudolph was not punished for his role that led to Myles Garrett ripping the helmet off of the Steelers quarterback's head, and then hitting him with it.
While Garrett's actions were clearly more severe, leading to an indefinite suspension by the league, many felt Rudolph was not without blame and that he got off the hook too easily. Rudolph had escalated the situation on the ground as cameras caught him attempting to rip off Garrett's helmet before anything else took place.
The video also shows an apparent kick to Garrett's groin, although that looked more incidental than purposeful.
MORE: Will Myles Garrett face legal action for hitting Mason Rudolph?
Rudolph then extended the skirmishby chasing Garrett after his helmet was ripped off.
ESPN's Max Kellerman was especially vocal about Rudolph's role (his comments were made before the suspensions were announced) during Friday morning's "First Take."
"Mason Rudolph needs to be suspended at least a game," Kellerman said. "He was the one who started the fight, tried to take Myles Garrett's helmet off."
Kellerman wasn't the only one who thought Rudolph got off easy, and that was before the suspensions were announced. Several fans and even players pointed out that Rudolph escalated the incident.
While Rudolph wasn't announced in the initial punishments handed down by the NFL, it doesn't exactly mean he's clear. The NFL said in its statement that "additional discipline for other players will be forthcoming through the standard accountability process including those players that left the bench to enter the fight area."
Having said that, though, it seems if Rudolph weregoing to be punished harshly by the league he would have been included in the initial statement by the league. He seems to have avoided suspension completely, but NFL Media'sTom Pelissero says Rudolph will be fined for his role. It's not clear when, or how much.


|
Nano102_112 Series BSP
V3.03.002
The Board Support Package for Nano102_112 Series
|
#include <Nano1X2Series.h>
Data Fields | |
| __IO uint32_t | CTL |
| __IO uint32_t | DMASAR |
| uint32_t | RESERVE0 [1] |
| __IO uint32_t | DMABCR |
| uint32_t | RESERVE1 [1] |
| __I uint32_t | DMACSAR |
| uint32_t | RESERVE2 [1] |
| __I uint32_t | DMACBCR |
| __IO uint32_t | DMAIER |
| __IO uint32_t | DMAISR |
| uint32_t | RESERVE3 [22] |
| __IO uint32_t | WDATA |
| __IO uint32_t | SEED |
| __I uint32_t | CHECKSUM |
@addtogroup DMA Direct Memory Access Controller(DMA) Memory Mapped Structure for DMA Controller
Definition at line 1719 of file Nano1X2Series.h.
| __I uint32_t DMA_CRC_T::CHECKSUM |
| Bits | Field | Descriptions |
|---|---|---|
| [31:0] | CRC_CHECKSUM | CRC Checksum Register |
| This field indicates the CRC checksum |
Definition at line 1912 of file Nano1X2Series.h.
| __IO uint32_t DMA_CRC_T::CTL |
| Bits | Field | Descriptions |
|---|---|---|
| [0] | CRCCEN | CRC Channel Enable |
| Setting this bit to 1 enables CRC's operation. | ||
| When operating in CRC DMA mode (TRIG_EN = 1), if user clear this bit, the DMA operation will be continuous until all CRC DMA operation done, and the TRIG_EN bit will asserted until all CRC DMA operation done. | ||
| But in this case, the CRC_DMAISR [BLKD_IF] flag will inactive, user can read CRC result by reading CRC_CHECKSUM register when TRIG_EN = 0. | ||
| When operating in CRC DMA mode (TRIG_EN = 1), if user want to stop the transfer immediately, user can write 1 to CRC_RST bit to stop the transmission. | ||
| [1] | CRC_RST | CRC Engine Reset |
| 0 = Writing 0 to this bit has no effect. | ||
| 1 = Writing 1 to this bit will reset the internal CRC state machine and internal buffer. | ||
| The contents of control register will not be cleared. | ||
| This bit will be auto cleared after few clock cycles. | ||
| Note: When operating in CPU PIO mode, setting this bit will reload the initial seed value | ||
| [23] | TRIG_EN | Trigger Enable |
| 0 = No effect. | ||
| 1 = CRC DMA data read or write transfer Enabled. | ||
| Note1: If this bit assert that indicates the CRC engine operation in CRC DMA mode, so don't filled any data in CRC_WDATA register. | ||
| Note2: When CRC DMA transfer completed, this bit will be cleared automatically. | ||
| Note3: If the bus error occurs, all CRC DMA transfer will be stopped. | ||
| Software must reset all DMA channel, and then trigger again. | ||
| [24] | WDATA_RVS | Write Data Order Reverse |
| 0 = No bit order reverse for CRC write data in. | ||
| 1 = Bit order reverse for CRC write data in (per byre). | ||
| Note: If the write data is 0xAABBCCDD, the bit order reverse for CRC write data in is 0x55DD33BB | ||
| [25] | CHECKSUM_RVS | Checksum Reverse |
| 0 = No bit order reverse for CRC checksum. | ||
| 1 = Bit order reverse for CRC checksum. | ||
| Note: If the checksum data is 0XDD7B0F2E, the bit order reverse for CRC checksum is 0x74F0DEBB | ||
| [26] | WDATA_COM | Write Data Complement |
| 0 = No bit order reverse for CRC write data in. | ||
| 1 = 1's complement for CRC write data in. | ||
| [27] | CHECKSUM_COM | Checksum Complement |
| 0 = No bit order reverse for CRC checksum. | ||
| 1 = 1's complement for CRC checksum. | ||
| [29:28] | CPU_WDLEN | CPU Write Data Length |
| When operating in CPU PIO mode (CRCCEN= 1, TRIG_EN = 0), this field indicates the write data length. | ||
| 00 = The data length is 8-bit mode | ||
| 01 = The data length is 16-bit mode | ||
| 10 = The data length is 32-bit mode | ||
| 11 = Reserved | ||
| Note1: This field is only used for CPU PIO mode. | ||
| Note2: When the data length is 8-bit mode, the valid data is CRC_WDATA [7:0], and if the data length is 16 bit mode, the valid data is CRC_WDATA [15:0]. | ||
| [31:30] | CRC_MODE | CRC Polynomial Mode |
| 00 = CRC-CCITT Polynomial Mode | ||
| 01 = CRC-8 Polynomial Mode | ||
| 10 = CRC-16 Polynomial Mode | ||
| 11 = CRC-32 Polynomial Mode |
Definition at line 1776 of file Nano1X2Series.h.
| __IO uint32_t DMA_CRC_T::DMABCR |
| Bits | Field | Descriptions |
|---|---|---|
| [15:0] | CRC_DMABCR | CRC DMA Transfer Byte Count Register |
| This field indicates a 16-bit transfer byte count number of CRC DMA |
Definition at line 1803 of file Nano1X2Series.h.
| __I uint32_t DMA_CRC_T::DMACBCR |
| Bits | Field | Descriptions |
|---|---|---|
| [15:0] | CRC_DMACBCR | CRC DMA Current Byte Count Register (Read Only) |
| This field indicates the current remained byte count of CRC_DMA. | ||
| Note: CRC_RST will clear this register value. |
Definition at line 1832 of file Nano1X2Series.h.
| __I uint32_t DMA_CRC_T::DMACSAR |
| Bits | Field | Descriptions |
|---|---|---|
| [31:0] | CRC_DMACSAR | CRC DMA Current Source Address Register (Read Only) |
| This field indicates the source address where the CRC DMA transfer is just occurring. |
Definition at line 1817 of file Nano1X2Series.h.
| __IO uint32_t DMA_CRC_T::DMAIER |
| Bits | Field | Descriptions |
|---|---|---|
| [0] | TABORT_IE | CRC DMA Read/Write Target Abort Interrupt Enable |
| 0 = Target abort interrupt generation Disabled during CRC DMA transfer. | ||
| 1 = Target abort interrupt generation Enabled during CRC DMA transfer. | ||
| [1] | BLKD_IE | CRC DMA Transfer Done Interrupt Enable |
| 0 = Interrupt generator Disabled during CRC DMA transfer done. | ||
| 1 = Interrupt generator Enabled during CRC DMA transfer done. |
Definition at line 1848 of file Nano1X2Series.h.
| __IO uint32_t DMA_CRC_T::DMAISR |
| Bits | Field | Descriptions |
|---|---|---|
| [0] | TABORT_IF | CRC DMA Read/Write Target Abort Interrupt Flag |
| 0 = No bus ERROR response received. | ||
| 1 = Bus ERROR response received. | ||
| Software can write 1 to clear this bit to zero | ||
| Note: The CRC_DMAISR [TABORT_IF] indicate bus master received ERROR response or not. | ||
| If bus master received ERROR response, it means that target abort is happened. | ||
| DMA will stop transfer and respond this event to software then go to IDLE state. | ||
| When target abort occurred, software must reset DMA, and then transfer those data again. | ||
| [1] | BLKD_IF | Block Transfer Done Interrupt Flag |
| This bit indicates that CRC DMA has finished all transfer. | ||
| 0 = Not finished yet. | ||
| 1 = Done. | ||
| Software can write 1 to clear this bit to zero |
Definition at line 1871 of file Nano1X2Series.h.
| __IO uint32_t DMA_CRC_T::DMASAR |
| Bits | Field | Descriptions |
|---|---|---|
| [31:0] | CRC_DMASAR | CRC DMA Transfer Source Address Register |
| This field indicates a 32-bit source address of CRC DMA. | ||
| Note : The source address must be word alignment |
Definition at line 1789 of file Nano1X2Series.h.
| uint32_t DMA_CRC_T::RESERVE0[1] |
Definition at line 1790 of file Nano1X2Series.h.
| uint32_t DMA_CRC_T::RESERVE1[1] |
Definition at line 1804 of file Nano1X2Series.h.
| uint32_t DMA_CRC_T::RESERVE2[1] |
Definition at line 1818 of file Nano1X2Series.h.
| uint32_t DMA_CRC_T::RESERVE3[22] |
Definition at line 1872 of file Nano1X2Series.h.
| __IO uint32_t DMA_CRC_T::SEED |
| Bits | Field | Descriptions |
|---|---|---|
| [31:0] | CRC_SEED | CRC Seed Register |
| This field indicates the CRC seed value. |
Definition at line 1900 of file Nano1X2Series.h.
| __IO uint32_t DMA_CRC_T::WDATA |
| Bits | Field | Descriptions |
|---|---|---|
| [31:0] | CRC_WDATA | CRC Write Data Register |
| When operating in CPU PIO (CRC_CTL [CRCCEN] = 1, CRC_CTL [TRIG_EN] = 0) mode, software can write data to this field to perform CRC operation;. | ||
| When operating in CRC DMA mode (CRC_CTL [CRCCEN] = 1, CRC_CTL [TRIG_EN] = 0), this field will be used for DMA internal buffer. | ||
| Note1: When operating in CRC DMA mode, so don't filled any data in this field. | ||
| Note2:The CRC_CTL [WDATA_COM] and CRC_CTL [WDATA_RVS] bit setting will affected this field; For example, if WDATA_RVS = 1, if the write data in CRC_WDATA register is 0xAABBCCDD, the read data from CRC_WDATA register will be 0x55DD33BB |
Definition at line 1888 of file Nano1X2Series.h.


{{description}}
This slot could be yours! Submit your talk now.  You want to show it to the community? Drop us a line on your topic on Twitter.